Guides
Progressive Rollouts
Ship a feature in stages with one flag — a single customer first, then a deterministic percentage, a dated ramp, general availability, and the kill switch — with rules that read the contract or the caller's context.
Kaiten Sushi Shop is redesigning its ordering flow. The seeded new-ordering-flow flag already exists, enabled, with on/off variants, off as the default, and a single rule that turns it on for Demo Restaurant. This guide takes that flag through a full rollout — one customer, a percentage, a dated ramp, everyone — and back off again, without a deploy at any stage.
Rules are evaluated top to bottom, first match wins; no match serves the default variant. A rule is a CEL expression over two kinds of facts: what the server knows about the instance (__kaiten.license, __kaiten.entitlements, __kaiten.customer, __kaiten.instance, __kaiten.deploymentZone) and whatever the caller puts in the evaluation context. You need $KAITEN_API and a token carrying write:feature_flags, as set up in the quickstart, and a stack seeded with the Kaiten Sushi Shop dataset.
Stage 1 — one customer first
This is the seeded state. Open Feature Flags, then new-ordering-flow, then its Targeting tab: one rule, __kaiten.customer.slug == 'demo-restaurant', serving on. Evaluate it for the demo instance and for a real customer:
curl -X POST "$KAITEN_API/ofrep/v1/evaluate/flags/new-ordering-flow" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"context": {"targetingKey": "demo-restaurant", "kaiten": {"instanceSlug": "demo-restaurant-prod"}}}'{"key": "new-ordering-flow", "value": true, "variant": "on", "reason": "TARGETING_MATCH"}curl -X POST "$KAITEN_API/ofrep/v1/evaluate/flags/new-ordering-flow" \
-H "Authorization: Bearer $KAITEN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"context": {"targetingKey": "sakura-tokyo", "kaiten": {"instanceSlug": "sakura-tokyo-prod"}}}'{"key": "new-ordering-flow", "value": false, "variant": "off", "reason": "DEFAULT"}The customer is a server fact: the caller sent only the instance slug, and __kaiten.customer.slug was resolved from it. Nothing the caller sends can override a __kaiten fact.
Stage 2 — a deterministic percentage
Keep the Demo Restaurant rule first, and add a second rule below it that splits everyone else.
- On the flag's page, click Configure: the flag's tabs are read-only, and editing happens there. Open Targeting Rules and click Add Rule.
- In Create Targeting Rule, choose the Targeting Type
Rollout Percentage: Name10% rollout, CEL Expressiontrue, and under Percentage Distributiononat 10 andoffat 90. Click Save. - Keep
Demo Restaurantabove the new rule and click Update Feature Flag. The Targeting tab now reads:Demo Restaurant→on;10% rollout→ split.
Evaluate for sakura-tokyo-prod again. It now lands in one of the two arms, and the reason says so:
{"key": "new-ordering-flow", "value": false, "variant": "off", "reason": "SPLIT"}The assignment is deterministic: the bucket is computed from the flag's key and the evaluation's targeting key, so the same subject receives the same variant on every evaluation of this flag, across restarts and replicas. It is also independent per flag — the flag's key salts the hash — so the subjects that land in the first 10% here are not the same 10% for the next rollout. To widen, edit the distribution: 25/75, 50/50, 100/0.
Stage 3 — a dated ramp
Instead of editing weights by hand, let the share grow on a schedule. A date rollout rule interpolates linearly between two points — variant, percentage, date at the start and at the end:
{
"type": "rollout_date",
"name": "Ramp to everyone in two weeks",
"rule": "true",
"start": {"variant": "off", "percentage": 10, "date": "2026-09-14T00:00:00Z"},
"end": {"variant": "on", "percentage": 100, "date": "2026-09-28T00:00:00Z"}
}Put it in targetings in place of the percentage rule (same PUT as above). The growing share receives the end variant and the rest the start variant: before 14 September every subject receives off; on 14 September 10% receive on; on 21 September about 55%; from 28 September all of them. Evaluations inside the ramp report SPLIT. Outside the growing share, the rule itself serves off — a date rollout always serves one of its two variants, so evaluation does not continue to the default — and the flag stays off for the rest until their turn.
Stage 4 — general availability
When the ramp reaches 100%, make the state explicit: set the default variant to on and remove the rollout rules. Every subject now receives on with reason DEFAULT (or STATIC when no rule is left at all), and the flag reads as what it is — shipped. Delete the flag only when the old code path is gone from your product.
The kill switch
Something breaks in the redesigned flow. Two ways back, both instant:
- Hard — switch the flag to disabled (
"enabled": false). Every evaluation returns the default variant with reasonDISABLED, and no rule is evaluated — not even the Demo Restaurant rule. This is the emergency stop. - Soft — keep the flag enabled and flip the default variant to
offwhile leaving the rules in place: Demo Restaurant keeps the new flow, everyone else falls back. This is the controlled retreat once the rollout rules are gone: a percentage or date rule still in place keeps serving its own variants, whatever the default.
Both are one field on the flag and one FEATURE_FLAG_UPDATED event, recorded in the organization's audit trail with the flag as it stood after the change — so the trail dates the switch, and every rule edit before it. Evaluations are recorded there too, on the console's Audit trail page: each entry carries the flag, the variant served, the reason and the rule that matched, for the organization and without an Instance — recording the Instance on that entry is in progress Coming soon. The trail says how often on was served before the switch was thrown, not to whom; Answer who had access shows how the contract's own history answers that. In the console, the kill switch is the Enabled switch of Configure, or the switch on the flag's card in the List view of Feature Flags, which asks for confirmation.
Rules that read the contract, and rules that read the caller
The rollout above targets by customer and by chance. Two more sources of truth are available to a rule, and the seeded flags use both:
- The contract.
promo-bannerserves itssalmon-weekpayload where__kaiten.license.familySlug in ['standard', 'premium']— the family, so a new version of either product keeps matching;delivery-tracking-enabledreads a grant with__kaiten.entitlements['delivery-tracking'] == true. Both facts are the License of the customer thetargetingKeynames, read from its oldest instance. A rollout can be gated the same way —__kaiten.license.type == 'PAID'as the condition of a percentage rule limits the experiment to paying customers. Gate a feature by License builds this chain end to end. - The caller's context.
analytics-dashboard-enabledcombines a server fact with a value your product sends:__kaiten.customer.slug == 'sakura-tokyo' && orderCount > 10, evaluated with{"context": {"targetingKey": "sakura-tokyo", "kaiten": {"instanceSlug": "sakura-tokyo-prod"}, "orderCount": 14}}. Anything outside__kaitenis yours — and stays out of the audit trail, which records the decision but never the context.
Order rules from most specific to least: a named customer before a License-wide rule, a License-wide rule before a percentage split. The first match wins, so a broad rule placed first hides everything below it.
A typo in one expression does not flip the flag for everyone: the value falls through to the next rule or the default, and the failure is reported in the response metadata under kaiten.brokenTargetingRules, where the rule's author sees it. Check it after every edit to a rule.
Where to go next
Meter Usage and Enforce Limits
Report consumption against NUMBER grants on three seeded instances — a hard cap that refuses the next report, an allowance that accepts a report past the cap, an unlimited grant — then correct a counter, wire the events, and design a quota of your own.
Deploy with CI/CD
Drive the release pillar from your pipeline — register the component version on build, cut the release, and record each landing zone by zone, so the deployment journal is written by the system that ships.