Multi-context identity

Multi-context identity lets one flag evaluation target several kinds of entity at once: a user, their organization, their device, and any other kind your project declares. You pass one or more typed contexts instead of a flat bag of attributes. Each context has its own kind, key, and attributes. A single flag can then match on user plan, organization tier, and device OS, all in one call.

What it is

The EvalContext type is a union of two shapes. A single context has a kind (any string; 'user' is conventional), a bucketing key, and any number of targeting attributes. Bucketing means sorting a visitor into a group by a consistent hash of their id, so the same visitor always lands in the same group. A multi-context is keyed at the top level by kind: 'multi'. It nests one single context per entity type you want to describe.

TypeScript
// Single context: the most common caseconst userCtx = {  kind: 'user',  key: 'u_123',  plan: 'pro',  country: 'US',}// Multi-context: user + org + device in one callconst multiCtx = {  kind: 'multi',  user: { kind: 'user', key: 'u_123', plan: 'pro' },  organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },  device: { kind: 'device', key: 'd_abc', os: 'ios' },}
TypeScript15 lines

A worked example

Say Acme Corp is one of your customers, with 50 employees using your product. Acme is on your enterprise tier. You want a rule that turns a flag on for every enterprise org, all at once, rather than deciding per person.

With a single user context, you have no clean way to bucket by organization. Each of the 50 people has their own hash, so they could land in different variations (one specific version being tested, control or a challenger) even though they work at the same company. With multi-context, the rule sets hashAttribute: "organization.key" and buckets on o_acme instead. All 50 employees hash the same way and land in the same variation together. A condition on the same call can still check one employee's own user attributes, their role, say, if the rule needs to.

When to use it

Use multi-context whenever a flag rule needs to target attributes from more than one entity. Common cases:

  • A B2B rollout. Turn a flag on for every user inside a specific organization tier, but not for the same user in a lower-tier org.
  • A device-specific gate that still respects plan. You don't want to bucket by user key when the identifier that matters is the device.
  • Workspace-level flags. Bucket by the workspace key, so every member lands in the same variation, while a condition still checks the requesting user's own role.
Info

You do not need multi-context to read org attributes. You can attach org properties directly to a single user context. Reach for multi-context when you want to bucket by a non-user key, or when your targeting conditions span more than one entity independently.

When NOT to use it

  • Every rule only ever cares about the person. Nothing in your targeting needs to bucket by organization, device, or any other non-user key. A single user context with a few extra attributes does the same job, with less code.
  • You only need to read, not bucket, an org-level fact. Per the callout above, attaching org properties straight onto the user context is simpler than building a multi-context. That works fine as long as you don't need org-level bucketing.
  • You're targeting a single custom attribute, not a whole other entity. A device ID as one more user attribute is fine. Reach for a device context only when you actually need to bucket by it.

How it works

Each flag rule declares a hashAttribute in dotted notation: 'user.key', 'organization.key', or any custom kind. The evaluator reads the matching key from the context and uses it for traffic bucketing. It uses the same MurmurHash3 algorithm every rule uses: a fast, deterministic hash, meaning the same input always produces the same output. An audience condition (a targeting rule inside a named, reusable group of visitors) also declares a contextKind. A condition like organization.tier === 'enterprise' is checked against only the organization sub-context, never the user.

For the datafile shape (the small file your SDK downloads, listing every live experiment, flag, and rule), every audience condition carries an optional contextKind field. A rule without one falls back to the primary context. That is the 'user' sub-context of a multi-context, or the context itself when it is already a single context.

Private attributes

Any context can declare _meta.privateAttributes: a list of attribute keys that must never appear in a decision log entry or an exposure event. An exposure is the moment a visitor is actually counted in an experiment, usually when they see the part being tested. The SDK redacts private values to [REDACTED] before any sink receives them. This is the only sanctioned way to pass PII into the evaluator without it leaking downstream.

TypeScript
const ctx = {  kind: 'user',  key: 'u_123',  email: 'alice@example.com',   // PII  plan: 'pro',  _meta: {    privateAttributes: ['email'],    anonymous: false,  },}
TypeScript10 lines

Typed context schema

For compile-time safety, use defineContextSchema from @avsbhq/utils to declare the expected attribute shape for each kind. See the Typed Contexts concept page for a full walkthrough.

Per-SDK usage

For the @avsbhq/browser SDK:

TypeScript
import { AvsbClient } from '@avsbhq/browser'const client = new AvsbClient({ sdkKey: 'sdk_production_xxxxxxxxxxxxxxxx' })await client.onReady()client.identify({  kind: 'multi',  user: { kind: 'user', key: 'u_123', plan: 'pro' },  organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },})const flag = client.getFlag('new_billing_ui', false)
TypeScript12 lines

Server-side, the same operation (create the server, get ready, build a multi-context, evaluate) in three languages:

import { AvsbServer } from '@avsbhq/node'const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY! })await server.onReady()const flag = server.getFlag('new_billing_ui', false, {  kind: 'multi',  user: { kind: 'user', key: 'u_123', plan: 'pro' },  organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },})
TypeScript10 lines
Was this helpful?