Contexts
A context is the thing you pass to the SDK on every flag evaluation. It says “here is who, or what, is asking.” Targeting rules match against fields on the context.
client.evaluate("checkout-v2", false, { targetingKey: "user-123", user: { plan: "pro", email: "alice@example.com" },});The shape
Section titled “The shape”A context always has a targeting key, a string that uniquely identifies the thing being evaluated. Everything else is structured under one or more context kinds:
{ targetingKey: "user-123", user: { plan: "pro", email: "alice@example.com" }, organization: { id: "acme", region: "eu" }, device: { os: "ios", appVersion: "4.2.1" }}You can pass any combination of kinds. A flag rule can reference any kind that exists in the project’s context kinds registry.
The targeting key
Section titled “The targeting key”The targeting key is special. It is what feat hashes for percentage rollouts: the same targeting key always falls into the same bucket while the rollout weights are unchanged. So:
- Use a stable per-user identifier (your internal user ID, an email if it never changes).
- Do not use a value that changes per request (a session ID, a request ID): users will hop between variations on every page load.
For anonymous traffic, mint a stable random ID and persist it (cookie, localStorage, mobile keychain) so a user keeps the same bucket across sessions. The web SDK does this for you when configured with anonymous: { storage: "localStorage" }.
Multiple kinds at once
Section titled “Multiple kinds at once”A single context can carry several kinds. This is how you express “the user is X, and their organization is Y, and their device is Z” in one evaluation. A rule can reach into any of them:
user.plan is one of ["pro", "enterprise"]organization.id is one of ["acme", "beta-org"]device.os is "ios"All three are AND’d within the rule. See Targeting rules for the rule structure.
Anonymous contexts
Section titled “Anonymous contexts”An anonymous context represents a visitor you have not identified yet. The shape carries a boolean on the user kind:
{ targetingKey: "8f3c9c2c-...", user: { key: "8f3c9c2c-...", anonymous: true },}You still need a targeting key, but it is a random ID rather than your internal user ID. Persist it client-side (a cookie, localStorage, a mobile keychain) so the same visitor lands in the same percentage-rollout bucket on every page load. The web SDK does this for you when configured with anonymous: { storage: "localStorage" }:
const client = new FeatWebClient({ envKey: "FEAT-CLIENT-...", anonymous: { storage: "localStorage" },});It mints a UUID on first run, writes it to feat:anonymousKey, and re-reads it on subsequent loads.
When to use it
Section titled “When to use it”- Marketing-site or landing-page traffic, before signup.
- Mobile app first launch, before the user creates an account.
- Edge or middleware code that fires before the auth layer has resolved a user.
Targeting it
Section titled “Targeting it”anonymous is a regular boolean attribute under the user kind, so rules match it the same way as any other field:
user.anonymous is one of [true] → only anonymous visitorsuser.anonymous is_empty → only identified usersA common pattern is to bucket anonymous traffic separately from logged-in users, so an experiment on the marketing site does not contaminate one running inside the product.
Transitioning to identified
Section titled “Transitioning to identified”When a visitor signs in, swap the SDK context for the identified shape:
await client.setContext({ targetingKey: "user-123", user: { plan: "pro", email: "alice@example.com" },});The targeting key changes, so the visitor enters a different percentage-rollout bucket on the next evaluation. If you want continuity across the login boundary (same bucket before and after), keep the previously-minted anonymous key as the targeting key and just attach the identified attributes alongside.
SDK support
Section titled “SDK support”Anonymous-context evaluation works on every SDK because anonymous is a regular attribute. Auto-mint and persistence are only provided where they belong, on the client side:
| SDK | anonymous: true support |
|---|---|
| Web SDK | Auto-mint and persist via anonymous: { storage: "localStorage" } config, plus generic attribute targeting. |
| OpenFeature on the web | Pass-through: whatever you set via OpenFeature.setContext(...) flows into the evaluator, including a manual user: { anonymous: true }. |
| Node.js SDK, Ruby SDK, Python SDK, Go SDK | Generic attribute targeting: the caller crafts the context (user: { anonymous: true }) and the evaluator reads it through the same path it reads any other attribute. Server-side runtimes don’t need a persistence helper. |
What the SDK does with it
Section titled “What the SDK does with it”The SDK sends nothing per evaluation. The whole context stays on your machine; evaluation runs locally against the datafile. The dashboard never sees individual user contexts unless you opt in to event reporting (which feat does not require).
That property is why feat does not retain a “contexts list” the way some platforms do: there is no inventory of users to leak.
OpenFeature
Section titled “OpenFeature”The context shape lines up with the OpenFeature evaluation context. If you write your code against OpenFeature, the same context you pass through OpenFeature.setContext(...) works with the feat Provider unchanged.
Related
Section titled “Related”- Context kinds for declaring what kinds you use.
- Targeting rules for matching on context fields.
- Percentage rollouts for the hashing behavior.