Skip to content

Flags

A flag is a named primitive whose value can change at runtime without redeploying. Two flag types ship today:

  • Boolean flagstrue or false. The everyday kill switch and gradual-rollout shape.
  • String flags — one of a small set of named variants (e.g. "midnight", "classic", "high-contrast"). Used for A/B/n splits and theme switches.

Every flag carries:

  • A key — unique within its project, e.g. checkout.new-flow.
  • A typeboolean or string.
  • A default value — what callers get when no rule matches.
  • A list of rules — checked top-to-bottom; the first match wins.
  • A description (optional) — surfaced in the dashboard and in audit context.

Project scope vs. environment scope

A flag is created at the project level. Project-scope identity (key, type, description) is shared across every environment in the project. Each environment then carries its own state: the per-env list of rules and the per-env default value.

The split lets you keep the same flag definition while shipping different rollouts in dev, staging, and production. When you create a flag, every existing environment in the project is seeded with the default value and rules you supplied, atomically — no environment is ever left with a stale or missing flag.

When you create a new environment, every existing flag in the project gets a default-state row inserted automatically. The new environment evaluates as if it were the original project from request one.

Reading a flag

http
GET /api/v1/envs/{envId}/flags/{key}

Returns the joined view: project-scope identity plus this environment's rules and default. The response carries an ETag you can supply on a subsequent PATCH for optimistic concurrency.

Editing a flag

Three shapes of edit, each scoped to what changes:

Project-scope metadataPATCH /api/v1/projects/{id}/flags/{key}. Updates the description. Doesn't touch evaluation, doesn't rematerialize rulesets.

Per-environment statePUT /api/v1/envs/{envId}/flags/{key}/state. Full-replace of (rules, defaultValue) for this environment only. Both fields are required; rules: [] is valid and means "always return the default value."

Project-scope deletionDELETE /api/v1/projects/{id}/flags/{key}. Cascades to every environment's state in one transaction.

A typical lifecycle

  1. Create with defaultValue: false and rules: []. Ships dark to every environment.
  2. Add a rule in dev that returns true for your own user ID. Click around. Verify.
  3. Add a percentage rollout in staging: [{ "if": [], "value": true, "rolloutPercent": 10 }] — 10% of traffic flips to true, sticky per user.
  4. Increase to 50%, then 100% in production as confidence grows.
  5. Default to true once the rollout reaches 100% and rules are no longer needed.
  6. Delete the flag (and the now-dead branch in the codebase).

The audit log records each step with the actor, the previous ruleset, the new ruleset, and a free-text reason.

See also

  • Rules and contexts — how rules combine conditions, what context attributes are, how percentage rollouts hash for stickiness.
  • Evaluation — how the evaluator runs rules and what the response looks like.
  • API reference: Flags — every endpoint, request shape, response shape.