Skip to content

Evaluation order

When your code calls getBooleanValue("checkout-v2", false, context), the SDK runs the following chain. Every feat SDK (Node, browser, Workers, Go, Python, Ruby) implements this identically, so the same datafile, context, and flag key always produce the same result and the same reason.

1. Archived flag → off variation reason: DISABLED
2. isEnabled is false → off variation reason: DISABLED
3. Individual target → target's variation reason: TARGETING_MATCH
4. First matching rule
├─ Rule serves variation → that variation reason: TARGETING_MATCH
└─ Rule serves rollout → bucketed variation reason: SPLIT
5. Default rule
├─ Single variation default → that variation reason: FALLTHROUGH
└─ Default rollout → bucketed variation reason: SPLIT
6. Nothing matched (rare) → off variation reason: DEFAULT
7. Error (missing flag, etc.) → caller-supplied default reason: ERROR

The chain is short-circuit: as soon as one step decides, evaluation returns.

If the flag is archived, the SDK returns the off-variation immediately. reason is DISABLED. This lets your code keep calling an archived flag without panic; it just always returns the safe value.

The flag’s per-environment toggle is off. SDK returns the off-variation with reason: DISABLED.

The off-variation is what you configure in the dashboard for the environment. For a kill switch, set it to the safe path.

If the flag has individual targeting entries, the engine checks for an exact (contextKindKey, contextKey) match against the context.

  • Multi-kind contexts are checked one kind at a time; the first match wins.
  • A match returns the target’s variation with reason: TARGETING_MATCH.

The engine walks the rules in position order. For each rule:

  • All conditions in a group are AND’d.
  • All groups in a rule are OR’d.
  • A condition whose attribute is missing in the context skips the rule (the rule does not match, evaluation moves on).
  • If the rule matches:
    • If variationId is set, return that variation with reason: TARGETING_MATCH.
    • If rollout is set, hash (targeting key on bucketing kind) + (flag key) + (per-flag salt) to a position in 1..100,000, pick the variation whose slice contains the position, return it with reason: SPLIT.

If no rule matches, evaluation proceeds to step 5.

See Operators for the operator catalog and Percentage rollouts for the bucketing detail.

Every environment config carries either:

  • A defaultVariationId: a single variation served when no rule matched. reason: FALLTHROUGH.
  • A defaultRollout: a weighted split, same bucketing as rule rollouts. reason: SPLIT.

Exactly one of the two is set. The “default rule” is the catch-all the dashboard shows at the bottom of the rules list.

If the default rule is malformed, the engine falls back to the off-variation with reason: DEFAULT. This step should not be reached in practice; it is the engine’s last-resort fallback.

If the flag is missing from the datafile, the variation lookup fails, or any other unrecoverable error occurs, the SDK returns the caller-supplied default value (the second argument to getBooleanValue) with reason: ERROR. errorMessage carries a human-readable explanation.

Common causes:

  • The flag was never created in this environment.
  • The flag was deleted (not archived; archived flags hit step 1, not step 7).
  • The variation an individual target or rule points to was deleted (should not happen; the API blocks variation deletion when references exist, but the engine handles it defensively).
ReasonWhat it means
TARGETING_MATCHAn individual target or a deterministic rule matched.
SPLITA percentage rollout (in a rule or in the default) decided.
FALLTHROUGHThe default rule’s single variation was returned.
DEFAULTEngine fallback to off; rare.
DISABLEDThe flag is archived or targeting is off.
ERRORSomething went wrong; the caller-supplied default was returned.

@feathq/feat-eval is the canonical engine in TypeScript. Every other SDK (Go, Python, Ruby) ports the same logic. If two SDKs disagree on a result for the same datafile and context, that is a bug.