Rules and overrides
Who gets what, the order it is decided in, and why a rule is not firing.
Targeting that does what you intended, and a way to find out why when it does not.
Rules decide who gets what. Overrides decide who gets what regardless.
Both live on a flag, both belong to exactly one environment, and both are read in a fixed order whenever a flag is evaluated.
The order a value is decided in
- Is the flag enabled in this environment? If not, the default value is served and nothing below runs.
- Does an override match this user? If so, its value wins immediately.
- Do any rules match? The first matching rule, by priority, decides.
- Otherwise the flag's value for this environment.
Worth reading twice, because the two most common surprises both live here. An override beats every
rule, no matter how specific the rule. And a disabled flag serves its default value, which is not
always false — a flag switched off while its default is true serves true.
Rules
A rule is a set of conditions and a pass percentage. Feature Management → your flag → Rules → New rule.
Conditions are combined with AND — every condition must match. For OR, write two rules.
What you can match on
A condition names an attribute, and the attribute is looked up in the context your SDK sends. These arrive as named fields:
| Attribute | From the evaluation context |
|---|---|
user_id | userId |
email | email |
country | country |
language | language |
platform | platform |
version | version |
sessionId | sessionId |
ipAddress, userAgent | the matching fields |
Anything else you put in attributes is matchable by its own name — plan, orgId, betaOptIn,
whatever you send.
An attribute the context does not contain never matches. Not an error, no warning: the rule simply does not fire. If a rule is not working, the first thing to check is whether the SDK is actually sending that attribute, and whether the name matches exactly.
Operators
equals, not_equals, in, not_in, contains.
All comparisons are string comparisons, byte for byte. US does not match us. A version of 1.10
is not greater than 1.9 — there is no numeric or semver comparison, so use in with an explicit
list when you need one.
in and not_in take several values; equals matches if any listed value matches.
Percentage rollouts
A rule's pass percentage decides what fraction of matching users get it. Set conditions to everyone and the percentage to 10, and you have a 10% rollout.
Bucketing is a hash of the user id and the flag key. It is stable: the same user stays in or out across requests and deploys, so moving 10% to 20% only ever adds users. Because the flag key is part of it, a user in the first 10% of one flag is not systematically in the first 10% of another.
A user with no id buckets identically to every other user with no id — a server rendering a page before login is either all in or all out. Pass a stable id as early as you can.
Order
Rules are evaluated by priority, top down, and the first match wins. Put the specific rule above the general one; a 50% rollout to everyone sitting above your internal-users rule means half your team gets the rollout instead of the feature.
Reorder them with the arrows on each row.
Overrides
An override pins one user to one value, ahead of every rule. Your flag → Overrides → New override.
Two uses, and they cover almost all of them: putting one customer on a feature early, and pinning yourself to a variant while testing something.
Overrides match on user id alone — the userId of the evaluation context. There are no conditions;
if you need conditions, you need a rule.
Give each override a reason. In three months it is the only thing that will explain why one account behaves differently from every other, and it is the difference between deleting it confidently and leaving it in place forever.
Both belong to one environment
A rule or override created while you are looking at staging belongs to staging. It does not exist
in your other environments, it is not listed there, and it cannot affect them.
This is deliberate, and it matches LaunchDarkly. There is no rule that spans environments, because a shared rule edited in one place would silently change the others — which, with an environment per tenant, means editing one customer and changing another.
The practical consequence: a rule you want everywhere has to be created in each environment. Switch environment, create it again. Copying an environment at creation time carries existing state across, but from then on they are independent.
When a flag is not doing what you expect
Work down the order at the top of this page.
- Check the environment first. The switcher decides which state, rules and overrides you are looking at, and a flag on in staging and off in production looks identical on screen until you read the switcher.
- Then check for an override on that user. It beats every rule, and it is the easiest thing to forget having created.
- Then the rule order. First match wins, so a broad rule above a narrow one hides it.
- Then the context. A condition on an attribute your SDK is not sending never matches. Log the context you are passing and compare the attribute names exactly.
- Then whether the flag is enabled at all in that environment — remembering that a disabled flag serves its default value, which may not be false.
The evaluation response carries a reason, and it distinguishes these cases: a rule match names the
rule, an override says so, and a flag that is off says that. It is faster than guessing.