Evaluation Flow
The steps Abrova Flags runs on every flag evaluation, and the reason each step returns. Understanding it helps you design better targeting rules and debug unexpected behavior.
Understanding how Abrova Flags evaluates a flag helps you design better targeting rules and debug unexpected behavior. Every evaluation follows the same steps and ends with a reason that tells you which step decided.
Where evaluation happens#
Flutter, React Native, Android, iOS, Next.js client
- Send the user context to Abrova Flags
- The service runs the steps below
- The app receives and caches the values
- Targeting rules never reach the device
Node.js, Next.js server
- Download the rules with the server secret
- Run the steps below inside your process
- You pass the context with every call
- No network call per evaluation
Both sides run the same steps with the same bucketing, so a user gets the same result from a client SDK and from a server SDK.
The Pipeline#
The first step that decides ends the evaluation. "Off" means enabled: false with the flag's off value.
- 1Is the flag enabled?If the flag is toggled off in this environment → off, reason
DISABLED - 2Check flag dependenciesEach flag this one depends on is evaluated first, in the same environment and for the same user. If one of them does not give the required result → off, reason
PREREQUISITE_FAILED - 3Evaluate targeting rulesIf the flag has targeting rules and they do not match the user context → off, reason
NOT_TARGETED - 4Rollout bucketingAt 100% everyone passes. At 0% nobody does. In between, the user is placed in a bucket by user id (or anonymous id). Outside the percentage → off, reason
OUTSIDE_ROLLOUT. No id at all → off, reasonNO_BUCKETING_ID - 5Variant selectionIf the flag has variants, one is picked by weight, again from the user id (or anonymous id) → on, reason
VARIANT, the value is the variant value - 6Return resultA flag without variants → on, value
true, reasonON
Flow Diagram#
┌────────────────────────┐
│ Is flag enabled? │
└────────┬───────────────┘
│ No → off, DISABLED
│ Yes ↓
┌────────────────────────┐
│ Check dependencies │
└────────┬───────────────┘
│ Fail → off, PREREQUISITE_FAILED
│ Pass ↓
┌────────────────────────┐
│ Evaluate targeting │
│ rules │
└────────┬───────────────┘
│ No match → off, NOT_TARGETED
│ Match ↓
┌────────────────────────┐
│ Rollout bucketing │
│ bucket of the user id │
│ inside the rollout %? │
└────────┬───────────────┘
│ Outside → off, OUTSIDE_ROLLOUT
│ No id → off, NO_BUCKETING_ID
│ Inside ↓
┌────────────────────────┐
│ Has variants? │
└────────┬───────────────┘
│ Yes → on, VARIANT (value of the variant)
│ No → on, ON (value true)Reasons#
Every result carries a reason. Read it with the detail getters of the SDKs (getDetail, getStringDetail and so on).
| Reason | Meaning |
|---|---|
| ON | The flag is on and has no variants. The value is true |
| VARIANT | The flag is on and a variant was served |
| DISABLED | The flag is off in this environment |
| PREREQUISITE_FAILED | A flag this one depends on did not give the required result |
| NOT_TARGETED | The targeting rules did not match the context |
| OUTSIDE_ROLLOUT | The user is outside the rollout percentage |
| NO_BUCKETING_ID | A rollout or split needed an id and the context has neither a user id nor an anonymous id |
| ERROR | The flag could not be evaluated. The off value is served |
| FLAG_NOT_FOUND | Set by the SDK: no such flag. Your default is returned |
| NOT_READY | Set by the SDK: no values or rules loaded yet. Your default is returned |
| TYPE_MISMATCH | Set by the SDK: a typed getter was used on a flag of another type. Your default is returned |
Off Values and Defaults#
There are two different fallbacks:
- The flag's off value is set on the flag in Abrova Console. It is served when the flag exists but the user does not get it (reasons
DISABLED,PREREQUISITE_FAILED,NOT_TARGETED,OUTSIDE_ROLLOUT,NO_BUCKETING_ID,ERROR). For a boolean flag it isfalseunless you set it. - The default in your code is the last argument of a getter. It is returned when the SDK has nothing to serve (reasons
FLAG_NOT_FOUND,NOT_READY,TYPE_MISMATCH).
// Client SDKs: (key, default)
const flow = flags.getString('checkout-flow', 'classic');
// Server SDKs: (key, context, default)
const flow = flags.getString('checkout-flow', { userId: 'user-123' }, 'classic');If a flag isn't behaving as expected, read its reason first: it names the step that decided. Then check that step: Is the flag enabled in this environment? Are its dependencies met? Do the targeting rules match the attributes you send? Is the user inside the rollout percentage? The flag's page in Abrova Console shows evaluation counts from the client SDKs, and from server SDKs that run with sendUsage: true.