Targeting & Attributes
The evaluation context
Section titled “The evaluation context”Every flag evaluation runs against a context you own:
{ identity, attributes }identityis opaque to Bridge — an email, internal UUID, anonymous ID, tenant ID, whatever your app uses. It powers deterministic bucketing (percentage rollouts) and observability.attributesis a free-form key-value map. You choose what to send; anything you send can be targeted.
Anonymous evaluation is a first-class case — on the frontend the SDK manages an anonymous identity for you (see anonymous targeting below).
Sending custom attributes
Section titled “Sending custom attributes”Two ways, and they compose:
Per-call context — for transient or call-site-specific values:
<script lang="ts"> import { useFlag } from '@nebulr-group/bridge-svelte/flags';
const checkout = useFlag('new_checkout', false, () => ({ attributes: { cart_size: cart.items.length, locale: navigator.language }, }));</script>const enabled = this.flags.flag('new_checkout', false, { identity: req.user?.id ?? tenantId, attributes: { cart_size: cart.items.length, country: req.headers['cf-ipcountry'] },});App-state attributes — for values that live in your app state (current cohort, route segment, workspace), set them once and every eval picks them up:
bridge.attributes.set('cohort', '2026-q2');On key collision, the value you supply always wins over anything Bridge contributes — and the admin UI marks the collision on the flag detail page so it’s never silently confusing.
Attributes appear automatically
Section titled “Attributes appear automatically”There is no attribute registration step. The first time a key appears in your eval context, it self-announces to Bridge and shows up in the rule builder’s attribute picker — namespaced as custom: (yours) or bridge: (contributed by Bridge subsystems), with its inferred type and sample observed values inline.
Adding a new targeting dimension is a one-line code change.
Discovery is per environment: keys discovered in dev never appear in prod.
The rule builder
Section titled “The rule builder”A flag is in one of three states:
| State | Meaning |
|---|---|
| Off for everyone | Kill switch — everyone gets the safe value |
| On for everyone | Everyone gets the on value |
| On for users matching a rule | Rule decides, with explicit when matched / when not matched values |
When you pick “matching a rule,” the builder appears inline on the flag’s page:
- Conditions combine with AND inside a branch; multiple branches are first-match-wins.
- Operators are type-aware —
gt/lt/betweenfor numbers,contains/regex/infor strings,exists/not_existsfor presence. The picker only offers operators valid for the attribute’s type. The full set:eq,neq,contains,not_contains,in,not_in,gt,lt,between,regex,exists,not_exists. - Percentage rollout is one input on the rule — “10% of matching users” — not a separate concept. Assignment is a deterministic hash of the flag key and identity, so a user stays in their bucket across sessions, devices, and server/client evaluations.
- A live “currently matches X users” preview shows the rule’s footprint before you save, and a would match after save comparison shows blast radius when editing.
Rules built once can be saved as a reusable group and shared across flags — you’ll discover that option on the flag page the first time you need it.
Flag value types
Section titled “Flag value types”Flags aren’t just booleans:
| Type | Use case | Example |
|---|---|---|
boolean | Feature gates | true / false |
string | Copy variants, theme, mode selection | "Pay now" |
number | Limits, thresholds | 50 |
json | Structured config | { window: 60, max: 100 } |
One API for all of them — the type is inferred from your default value:
const isDark = useFlag('dark_mode', false); // booleanconst cta = useFlag('checkout_text', 'Submit'); // stringconst limit = useFlag('max_uploads', 10); // numberconst cfg = useFlag('rate_limit', { window: 60, max: 100 }); // JSONIf an admin changes a flag to a different type than your default suggests, the SDK returns your default and logs a warning — it never throws into your app.
Anonymous targeting
Section titled “Anonymous targeting”On the frontend, the SDK generates an anonymous ID on first load and persists it, so anonymous visitors get stable bucketing across sessions — your marketing-page A/B test works before anyone signs up.
Choose how it’s stored at init, depending on your privacy posture:
| Mode | Storage | Sticky across sessions? |
|---|---|---|
persistent (default) | localStorage | Yes |
session | sessionStorage | Same tab only |
none | in-memory | Page lifetime only |
When a visitor signs up or logs in, call identify(userId) — the SDK switches to the known identity and links prior anonymous activity to it, so attribution stays continuous across the login boundary.
On the backend there is no ambient identity: pass identity per eval (the user, or a tenant ID for sticky per-tenant behavior), or pass nothing for system-level flags. See common patterns for the rollout implications.
- Observability — see who matches, where flags are read, and whether they’re alive
- Upgrade to the full platform — get
bridge:user.*andbridge:billing.*attributes for free