Target on email or PII without it landing in analytics
Use privateAttributes to target on email or tier while stripping those fields from every event the SDK emits.
You want to gate a beta on email endsWith "@acme.com", or target by a billing tier. The targeting needs those attributes — but you do not want raw emails and account identifiers flowing into your events store, where they end up in analytics, exports, and every downstream pipe that reads exposures.
privateAttributes gives you both: evaluate against the attribute locally, strip it before anything is sent.
Why it works. Evaluation runs in your process against the in-memory rule set — a private attribute never has to leave the process to be matched on. Marking it private changes only what the SDK transmits: the field is removed from every exposure and track event before it hits /collect.
Declare the sensitive fields once
import { configure, Client } from "@shipeasy/sdk/server";
configure({
apiKey: process.env.SHIPEASY_SERVER_KEY!,
privateAttributes: ["email", "ssn"],
});Now the same read targets on email but never serializes it into telemetry. Bind a Client to the user — the attributes you pass it are matched on, but the private ones never travel:
const flags = new Client({
user_id: "u_4f2a",
email: "ana@acme.com", // used for targeting…
plan: "pro", // normal attribute — stays in events
});
flags.getFlag("beta_program");
// …email never lands in the exposure eventA private attribute is used for evaluation and stripped from events. A normal attribute is used for evaluation and kept in events. Put anything you wouldn't want in analytics — PII, billing ids, security flags — on the private list.
Rollout & measurement plan
- Default to private for anything identifying. Email, phone, national id, internal account keys — if you'd hesitate to see it in a CSV export, mark it private.
- You can still target on it. Matching is unchanged; only transmission is affected. A rule on
email endsWith "@acme.com"works exactly the same. - Keep the bucketing unit non-private.
user_iddrives deterministic assignment and must travel — list the attributes you target on, not the identifier you bucket on.
Count an exposure only when the variant is actually seen
Resolve a variant in a prefetch or lazy component with logExposure:false, then log the exposure by hand the moment it renders.
Debug why a user isn't seeing a feature
Use getFlagDetail reasons to tell DEFAULT (rolled to 0%) from FLAG_NOT_FOUND (typo or stale bundle) from OFF (killed).