/Docs

Multi-Context Identity

A vs B flags can target multiple entity types simultaneously. Instead of a flat attribute bag, every evaluation call carries one or more typed contexts — each with a kind, a key, and its own attributes — so a single flag can match on user plan, organization tier, and device OS at the same time.

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. A multi-context is keyed at the top level by kind: 'multi' and nests one single context per entity type you want to describe.

ts
1// Single context — the most common case
2const userCtx = {
3 kind: 'user',
4 key: 'u_123',
5 plan: 'pro',
6 country: 'US',
7}
8
9// Multi-context — user + org + device in one call
10const multiCtx = {
11 kind: 'multi',
12 user: { kind: 'user', key: 'u_123', plan: 'pro' },
13 organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },
14 device: { kind: 'device', key: 'd_abc', os: 'ios' },
15}

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 where the flag should be on for any user inside a specific organization tier — but not for the same user in a lower-tier org.
  • A device-specific feature gate that also respects user plan — you do not want to bucket by user key when the meaningful identifier is the device.
  • Workspace-level flags where the bucketing should use the workspace key (so all members of the workspace land in the same variation) but the condition also checks the requesting user's role.
Info
You do not need multi-context to access org attributes. You can attach org properties directly to a single user context. Use multi-context when you want to bucket by a non-user key or when targeting conditions span multiple entity types independently.

How it works

Each flag rule declares a hashAttribute in dotted notation: 'user.key', 'organization.key', or any custom kind. The evaluator extracts the correct key from the context and uses it for traffic bucketing via MurmurHash3. Audience conditions also declare a contextKind, so a condition like organization.tier === 'enterprise' is evaluated against the organization sub-context only, not the user.

For the datafile shape, every audience condition gains a contextKind field. Rules without it default to 'user' for backwards compatibility.

Private attributes

Any context can declare _meta.privateAttributes — a list of attribute keys that must never appear in decision log entries or exposure events. The SDK redacts their values to [REDACTED] before any sink receives them. This is the only sanctioned way to pass PII into the evaluator without it leaking downstream.

ts
1const ctx = {
2 kind: 'user',
3 key: 'u_123',
4 email: 'alice@example.com', // PII
5 plan: 'pro',
6 _meta: {
7 privateAttributes: ['email'],
8 anonymous: false,
9 },
10}

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

@avsbhq/browser
ts
1import { AvsbClient } from '@avsbhq/browser'
2
3const client = new AvsbClient({ sdkKey: 'sdk_production_abc123' })
4await client.onReady()
5
6client.identify({
7 kind: 'multi',
8 user: { kind: 'user', key: 'u_123', plan: 'pro' },
9 organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },
10})
11
12const flag = client.getFlag('new-billing-ui', false)
@avsbhq/node
ts
1import { AvsbServer } from '@avsbhq/node'
2
3const server = new AvsbServer({ sdkKey: process.env.AVSB_SDK_KEY })
4await server.onReady()
5
6const flag = server.getFlag('new-billing-ui', false, {
7 kind: 'multi',
8 user: { kind: 'user', key: 'u_123', plan: 'pro' },
9 organization: { kind: 'organization', key: 'o_42', tier: 'enterprise' },
10})
avsb-python
python
1from avsb import AvsbServer
2
3server = AvsbServer(sdk_key=os.environ["AVSB_SDK_KEY"])
4server.wait_for_ready()
5
6ctx = {
7 "kind": "multi",
8 "user": {"kind": "user", "key": "u_123", "plan": "pro"},
9 "organization": {"kind": "organization", "key": "o_42", "tier": "enterprise"},
10}
11
12flag = server.get_flag("new-billing-ui", False, ctx)
avsb-go
go
1client, _ := avsb.NewServer(avsb.Options{SDKKey: os.Getenv("AVSB_SDK_KEY")})
2client.WaitForReady(context.Background())
3
4ctx := avsb.MultiContext{
5 User: avsb.Context{Kind: "user", Key: "u_123", Attrs: avsb.Attrs{"plan": "pro"}},
6 Organization: avsb.Context{Kind: "organization", Key: "o_42", Attrs: avsb.Attrs{"tier": "enterprise"}},
7}
8
9flag := client.GetFlag("new-billing-ui", false, ctx)