Why did my targeting rule not match?
Last updated:
Usually one of three things: an earlier rule matched first and stopped evaluation, the attribute your rule tests was never included in the evaluation context, or the value is present but a different type or case than the rule expects. Rules run top to bottom and the first match wins, so rule order is part of the logic.
A rule matches on an attribute, an operator, and a set of values. Three things have to line up, and any one of them can be quietly wrong.
1. An earlier rule already matched
Rules are evaluated in order and the first match wins. Evaluation stops there. So a broad rule sitting above a narrow one makes the narrow one unreachable:
1. plan equals "pro" -> serve true2. email equals "you@co.com" -> serve falseIf you are on the pro plan, rule 2 never runs. It is not broken. It is shadowed.
Order rules from most specific to most general. Individual users first, then segments, then broad attributes like plan or region. This is also why adding a new rule at the bottom of a long list often appears to do nothing.
2. The attribute is not in the context
Rules can only test what you send. An attribute you never included is absent, and a rule on it cannot match:
// Rule tests `plan`, context does not carry itclient.boolVariation('new-checkout', { user_id: 'u_123' }, false);
// Rule can now matchclient.boolVariation('new-checkout', { user_id: 'u_123', plan: 'enterprise' }, false);Context is supplied per call rather than stored, so this can differ between two call sites in the same app. A flag that works on one endpoint and not another is usually this.
The practical consequence: when a user upgrades their plan, targeting picks it up on the next request that carries the new value, because nothing is cached against the user.
3. The value does not compare the way you expect
Comparisons are exact. "Enterprise" does not equal "enterprise", and the string "true" is not the boolean true. Numeric IDs sent as numbers will not match rule values written as strings.
Pick the operator deliberately too. equals, contains, startsWith, endsWith and in are all available, and contains on an email domain behaves differently from endsWith in exactly the case you care about. A rule matching contains "@acme.com" also matches user@acme.com.attacker.net.
Reading the outcome
The reason string names what happened. rule-match:{id} tells you which rule won, so you can compare it against the one you expected. fallthrough means no rule matched at all, which points at cause 2 or 3 rather than ordering.
Rule structure, operators and segments are documented in User targeting and segments, with worked examples in the targeting rules guide.
Related questions
Do I need a user ID to evaluate a feature flag?
Only for percentage rollouts. Without a stable identifier a server SDK serves the control variation, and the hosted endpoint spreads traffic with no stickiness.
Why do two users get different variations?
That is what a percentage rollout does. Bucketing hashes the user ID with the flag key, so the split is per user and per flag, and never lines up across flags.
Why is my feature flag returning the default value?
Six reasons a Featureflip flag falls back to the default you passed, ordered by how often each one is the real cause, with the check for each.
Still stuck?
The docs cover every SDK, and the free Solo plan is enough to reproduce most of these locally.