Why did my targeting rule not match?

Last updated:

An earlier rule almost certainly matched first and stopped evaluation. Failing that, the attribute your rule tests never made it into the evaluation context, or the value is there but arrives as a different type or case than the rule expects. Rules run top to bottom and the first match wins, which makes rule order part of the logic.

A rule matches on an attribute, an operator, and a set of values. Any of those can be quietly wrong, and from the call site all three failures look the same.

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. There is nothing wrong with it. It simply never gets reached.

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.

One useful consequence of that is upgrades landing immediately. Nothing is cached against the user, so when someone moves to a new plan, targeting picks it up on the very next request that carries the new value.

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.

For the full operator list and how segments fit in, see User targeting and segments. The targeting rules guide works through the ordering problem above with real rules.

Still stuck?

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