Docs · Targeting
How targeting works
A flag has a default variation that everyone gets unless a rule says otherwise. Rules are ordered: the first one that matches wins. Within a rule, every condition must match (AND). An optional rollout % further narrows the match to a deterministic per-user bucket.
Evaluation order
for rule in flag.rules: # top to bottom
if every condition matches the user: # AND
if rule.rollout is set:
bucket = hash(user.id, flag.key) % 100
if bucket >= rule.rollout.percent:
continue # rolled out of this rule
return rule.serveVariation # first match wins
return flag.defaultVariation # nothing matched Worked example: name@example.com always on, 50% of everyone else
You want to ship a new checkout flow. Your tester is name@example.com — they should always see the new flow. Everyone else gets a 50% rollout so you can compare conversion.
Flag config
{
"key": "new-checkout",
"description": "Faster Stripe checkout flow",
"variations": [
{ "key": "on", "value": true },
{ "key": "off", "value": false }
],
"defaultVariation": "off",
"rules": [
{
// Rule 1: targeted user — always on
"conditions": [
{ "attribute": "email", "op": "equals", "value": "name@example.com" }
],
"serveVariation": "on"
},
{
// Rule 2: 50% of everyone else
"rollout": { "percent": 50 },
"serveVariation": "on"
}
]
} How it evaluates
For name@example.com: Rule 1’s condition matches → serve true. Rule 2 is never checked. For any other user: Rule 1 misses. Rule 2 has no conditions and a 50% rollout → ~half the users are bucketed in and get true; the rest fall through to the default (false). Bucketing is deterministic per user, so the same user always sees the same outcome until you change the rollout %.
| User | Rule 1 (email) | Rule 2 (50% bucket) | Result |
|---|---|---|---|
| name@example.com | ✓ match | — | true |
| alice@acme.io | miss | bucket 23 < 50 | true |
| bob@acme.io | miss | bucket 78 ≥ 50 | false |
| carol@acme.io | miss | bucket 23 < 50 | true |
Try it
Sign up for the 15-day trial and create a flag with these two rules. Then call client.isEnabled('new-checkout', { id: user.id, attributes: { email: user.email } }) — your tester will land on true, everyone else on a stable 50/50 split.
import nightroll from '@nightroll/js';
const client = nightroll.init('clt_live_…');
await client.ready();
const user = {
id: currentUser.id,
attributes: { email: currentUser.email }
};
if (client.isEnabled('new-checkout', user)) {
showNewCheckout();
} else {
showOldCheckout();
} Caveats
- Bucketing is per
(user.id, flag.key). The same user lands in the same bucket on every device until you change the rule. - If a user has no
id, rules with a rollout always miss (they can't be bucketed). Plain conditions still match. - Rules are ordered — drag-reorder isn't in v1, so delete and re-add to change priority.