Appearance
Rollouts
A rollout is how a flag or config value reaches users gradually. Rules decide who qualifies for the new value; the rollout decides how much of that audience gets it, ramping up over time with the safety primitives you need to ship to production.
Every rollout is scoped to one (flag, environment) or (config, environment) pair. A flag can have at most one live rollout per environment. Starting a new rollout supersedes the previous one.
Rollout vs rule
Two concepts that often get confused:
- A rule says "show the new value to enterprise customers in the US." That's targeting — who qualifies.
- A rollout says "of everyone the rule admits, give 10% the new value today, 25% tomorrow, and 100% by Friday." That's exposure — how much of the audience gets the change.
You use both together. A rule like { "if": {"field": "plan", "$equals": "enterprise"}, "value": true } admits enterprise contexts; the rollout then gates what fraction of those admitted contexts actually receive true. Contexts in the rule but outside the rollout's current percent fall through to the previous value.
States
A rollout moves through a small state machine:
| State | What it means |
|---|---|
active | The rollout is live. Either ramping on a schedule or holding at a percent. |
paused | Frozen at pausedAtPercent. Carries a pausedReason (see below). |
completed | Reached 100% and the new value is now the default. Terminal. |
cancelled | Operator backed out. Terminal. The flag returns to its previous value. |
Three reasons a rollout can be paused:
user— an operator clicked Pause in the dashboard or called the pause endpoint.approval_gate— the scheduler hit a ramp step that requires explicit approval and is holding at the previous step's percent until someone resumes.auto_rollback— a webhook fire from your alerting tool dropped the rollout to 0%. See Automatic rollback.
The resume verb dispatches on the reason. Resuming a user pause continues the ramp from where it stopped; resuming an approval_gate pause atomically advances to the next step; resuming an auto_rollback pause requires explicit operator intent because the rollout was dropped to 0%.
Percentage bucketing
Rollouts use the same hash-based bucketing as the rest of the evaluator. Given a context, Actuator computes a stable bucket from the rollout's seed and the context's target ID field (userId by default), then admits the context if its bucket falls under the current percent.
admit = xxhash64(rollout.seed + ":" + context[targetIdField]) mod 10000 < percent * 100Two important properties:
- Stickiness — the same context always lands on the same side of the cut. A user who saw the new value at 10% still sees it at 25% and at 100%.
- Monotonicity — increasing the percent only ever admits more contexts; never re-shuffles anyone already admitted. The ramp is smooth, not a re-roll at each step.
The seed defaults to <primitiveKey>:<envKey> and is locked once the rollout has advanced past 0%. Locking the seed means a cancel-and-recreate flow doesn't re-bucket the contexts you already admitted.
The bucketField defaults to the project's target ID field and can be overridden per rollout. Use it when you need to roll out by tenant, by device, or by anything other than the per-user default.
Allow-list
Some users always get the new value, regardless of percentage. The target IDs list is the rollout's allow-list: explicit target IDs short-circuit the percent gate and receive the candidate value immediately.
POST /api/v1/envs/{envId}/flags/{key}/rollout/target-ids/add
{ "targetIds": ["u_42", "u_43", "u_44"] }Allow-lists are designed for large scale — up to 100,000 IDs per bulk request, with no upper bound on the list size. Use them for internal users, beta cohorts, or migration carve-outs where you want explicit control over who's in regardless of where the percent currently sits.
Rollout plans
A rollout plan is an optional project-scoped template that describes a ramp. A plan supplies the ramp steps and cadence; a rollout attaches to a plan and inherits both.
A plan's ramp steps are an ordered list of { percent, holdForSeconds, requiresApproval? }:
json
{
"key": "standard-1d",
"name": "Standard 1-day ramp",
"ramp_steps": [
{ "percent": 1, "holdForSeconds": 3600 },
{ "percent": 10, "holdForSeconds": 7200 },
{ "percent": 50, "holdForSeconds": 14400, "requiresApproval": true },
{ "percent": 100, "holdForSeconds": 0 }
],
"cadence": "auto",
"blackout_days_of_week": [5, 6],
"blackout_timezone": "America/Los_Angeles"
}Two cadences:
auto— the scheduler advances to the next step afterholdForSecondselapses, skipping any blackout window. Approval-gated steps pause before advancing.manual— the operator drives every step withPOST /rollout/advance. The scheduler doesn't advance the rollout on its own.
When a rollout attaches to a plan, the plan's ramp steps are snapshotted onto the rollout. Editing the plan later does not retime any live rollout — a change you make midway through a ramp can't suddenly accelerate or reverse traffic that's already exposed. New rollouts pick up the updated plan from then on.
A rollout doesn't have to use a plan. Without one, the percent is whatever the operator set, and advancing means a manual PUT with a higher percent.
Safety primitives
Approval gates
Mark any ramp step with "requiresApproval": true. When the scheduler reaches that step, it pauses the rollout at the previous step's percent with pausedReason: "approval_gate". An operator confirms by calling POST /rollout/resume, which atomically approves the gate and advances to the gated step. Approval gates require cadence: "auto"; they're rejected on manual-cadence plans because the operator is already in the loop.
Blackout windows
Plans carry a list of days of the week (0–6, Sunday = 0) and an IANA timezone (America/Los_Angeles, Europe/London). The scheduler skips any step whose nextStepAt falls on a blackout day, deferring the advance to the next non-blackout day in the plan's timezone. Use blackouts to keep changes out of weekends, holidays, or your customer's known low-coverage windows.
Like ramp steps, blackout configuration is snapshotted onto the rollout at attach time. Editing the plan's blackout config doesn't retime live rollouts.
Automatic rollback
Wire your alerting tool to a stable webhook URL per rollout. When the alert fires, Actuator drops the rollout to 0% with pausedReason: "auto_rollback". The full setup is on the Automatic rollback page.
A typical lifecycle
- Create a plan at the project level:
[1%, 10%, 50% (approval), 100%]withcadence: "auto"and a weekend blackout. - Create a rollout on the flag in
staging, attaching the plan. The seed defaults to<flagKey>:staging. - Watch the scheduler ramp through 1% → 10% over the configured holds, skipping the weekend if it falls on one.
- The approval gate at 50% pauses the rollout. The on-call lead reviews dashboards, then resumes.
- The rollout reaches 100% and completes. The new value is promoted to the flag's default; the rollout is marked
completedand the row is preserved for audit.
The audit log records every transition with the actor, the previous state, the new state, and the reason — including auto-rollback webhooks identified by their secret.
See also
- Rules and contexts — the targeting model: how rules pick which contexts qualify.
- Automatic rollback — wire an alerting tool to pause a rollout when something goes wrong.
- API reference: Rollouts — every endpoint, request shape, response shape.