Feature Flags
Variants
Define the possible values a feature flag can resolve to — from boolean toggles to object payloads.
What are variants?
Every feature flag defines a set of variants — the possible values it can resolve to. When a flag is evaluated, it returns exactly one variant based on targeting rules or the default.
Each variant has:
- A name — a unique identifier within the flag (e.g.
"on","control","treatment-a") - A description — free text shown in the console
- A value — the payload, matching the flag's
type(boolean,string,number, orobject)
Boolean variants
The example below defines two variants with boolean payloads:
{
"variants": [
{ "name": "on", "description": "Feature visible", "value": true },
{ "name": "off", "description": "Feature hidden", "value": false }
],
"default_variant": { "type": "basic", "value": "off" }
}Use boolean flags for:
- Feature gates (enable/disable a capability)
- Kill switches (disable a broken feature)
- Gradual rollouts (target specific instances)
Object variants
For structured configuration, variants carry JSON payloads. This suits A/B testing, multi-variant experiments, and per-customer configuration.
{
"variants": [
{
"name": "control",
"description": "Current banner",
"value": {
"bannerText": "Welcome!",
"bannerColor": "#000000"
}
},
{
"name": "treatment-a",
"description": "Promotional banner",
"value": {
"bannerText": "Special Offer!",
"bannerColor": "#ff0000"
}
},
{
"name": "treatment-b",
"description": "Feature announcement banner",
"value": {
"bannerText": "New Feature!",
"bannerColor": "#00ff00"
}
}
],
"default_variant": { "type": "basic", "value": "control" }
}Use object flags for:
- A/B testing with different configurations
- Tiered feature configurations
- UI customization per customer segment
Default variant
Every flag must have a default_variant. This is what is served when:
- The flag is disabled (
enabled: false) — reasonDISABLED - No targeting rule matches the evaluation context — reason
DEFAULT(or, for abasicdefault,STATICwhen the flag has no rules at all)
An evaluation whose context fails validation — no targetingKey — serves no variant: it fails with the error code PROVIDER_FATAL.
The default takes one of three forms, the same three a targeting rule can serve:
| Form | Shape | Behavior |
|---|---|---|
basic | { "type": "basic", "value": "off" } | One fixed variant, named by its name |
rollout_percentage | { "type": "rollout_percentage", "distribution": { "on": 20, "off": 80 } } | A weighted split across variants — reason SPLIT when the flag has no rules, DEFAULT otherwise |
rollout_date | { "type": "rollout_date", "start": { "date": "…", "percentage": 0, "variant": "off" }, "end": { "date": "…", "percentage": 100, "variant": "on" } } | A percentage ramp from the start variant to the end variant between two dates — reason SPLIT when the flag has no rules, DEFAULT otherwise |
Rules for variants
| Rule | Detail |
|---|---|
| Unique names | Keep variant names unique within a flag: the API does not refuse a duplicate, and an evaluation serves the value of the last variant of that name |
| Three required fields | Every variant carries name, description, and value |
| Consistent value types | Keep every variant's value in the flag's type — boolean, string, number, or object |
| Default must exist | A basic default's value must be the name of one of the defined variants; a rollout default's distribution or steps must name defined variants |
Variant resolution flow
Evaluation request
│
├── Flag disabled? → return default_variant (DISABLED)
│
├── Evaluate targeting rules (top → bottom):
│ ├── Rule 1 matches? → return rule's variant
│ ├── Rule 2 matches? → return rule's variant
│ └── No match → return default_variant (DEFAULT)
│
└── Response: { key, variant, value, reason }The reason field in the response tells you why a particular variant was returned:
| Reason | Meaning |
|---|---|
TARGETING_MATCH | A targeting rule matched and served a basic variant |
SPLIT | A rollout_percentage or rollout_date distribution chose the variant |
DEFAULT | Rules exist but none matched; the default was served |
STATIC | The flag has no rules; the default was served |
DISABLED | Flag is disabled; the default was served without evaluating any rule |
ERROR | The context failed validation (no targetingKey); no variant was served. The response carries the error code PROVIDER_FATAL instead of a reason; ERROR is the reason the recorded evaluation carries |
Variant names are what OpenFeature SDKs surface as the variant of a result. Keep them short and descriptive — they appear in evaluation responses and logs.
Managing variants in the console
A flag's Variants tab is read-only: it lists each variant with its description, a preview of its payload, and whether the default uses it. Variants are edited in Configure, on Variants Configuration:
- Add a variant with Add Variant, and give it a name, a description and a value.
- Edit a variant in place — a rule that names a variant you rename must be updated with it.
- Delete a variant, after a confirmation.
A boolean flag is the exception: it keeps exactly two variants, named true and false when the flag is created, whose values are locked. They can be renamed, but variants cannot be added or deleted. The default is chosen on the next step, Default Variant. Update Feature Flag saves the whole definition.
Overview
Control which features are available to which instances with typed variants, CEL targeting rules, and OpenFeature compatibility.
Targeting Rules
CEL rules decide which variant an instance receives — what a rule can read under __kaiten, how rules are ordered and evaluated, percentage and date rollouts, and the three operations that check a rule before it is saved.