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 true
2. email equals "you@co.com" -> serve false

If 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 it
client.boolVariation('new-checkout', { user_id: 'u_123' }, false);
// Rule can now match
client.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.

Still stuck?

The docs cover every SDK, and the free Solo plan is enough to reproduce most of these locally.