Skip to content

Targeting & Attributes

A targeting rule is what makes an “on” flag return different values to different people, instead of the same value to everyone — “on for role = ADMIN”, “on for 10% of matching users”, “on for tenant.plan = ENTERPRISE”. You build one visually in Control Center, and it’s evaluated against a context your app supplies. This page covers the mental model: how a rule is structured, and where the values it matches against come from.

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-awaregt/lt/between for numbers, contains/regex/in for strings, exists/not_exists for 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.

Every condition in a rule — role equals ADMIN, project_count greater than 3 — matches against a value from the evaluation context, which is a context you own:

{ identity, attributes }
  • identity is opaque to Bridge — an email, internal UUID, anonymous ID, tenant ID, whatever your app uses. It powers deterministic bucketing (percentage rollouts) and observability.
  • attributes is 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).

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>

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.

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.

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);                   // boolean
const cta    = useFlag('checkout_text', 'Submit');            // string
const limit  = useFlag('max_uploads', 10);                    // number
const cfg    = useFlag('rate_limit', { window: 60, max: 100 }); // JSON

If 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.

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.