docs / how it works
How Flagger works
Flags are declared in your code, start off, and are switched on from the CLI, the API or an agent. Checks are fast and local, and anything Flagger doesn't recognize is off.
This describes the flag system that runs Sandboxer and Transformer in production today, which Flagger is built on. Flagger's hosted service, SDKs and the features marked "in development" are not available yet. Join the waitlist for early access.
Declare flags in code
Every flag is declared in your code with a key, a description, an owner and a default. Keys are lowercase letters, digits and underscores, starting with a letter, up to 63 characters.
{
key: "new_billing_page",
description: "Redesigned billing page",
owner: "payments",
default: false,
}
When your app starts, any declared flag that doesn't exist yet is added as off. A flag someone already switched is never overwritten by a deploy. There's no way to create or delete flags through the API: flags come from code, so the code and the flag list can't drift apart.
States
Each flag is in exactly one of five states:
| State | Who sees the feature |
|---|---|
off | No one |
admins | Platform admins only |
targets | The users and workspaces in the rules, plus admins |
percent | A sticky share of users, plus admins |
on | Everyone |
A typical rollout moves off → admins → targets → percent → on, and then the flag is removed from the code.
Targeting rules
Rules are stored with every flag, whatever its state, so switching state never loses a target list.
- Users: user ids or emails. Emails match without regard to case, and only verified emails count.
- Workspaces: workspace ids, matched against every workspace the user belongs to.
- Percent: 0 to 100.
How percentage buckets work
For a flag in percent, take the first 4 bytes of sha256(key + ":" + userId), read them as a big-endian number, and take that modulo 100. The user is in if the result is below the percentage. The same user always lands in the same bucket for a flag, so raising the percentage only ever adds people.
Checking a flag
if (flag(ctx, "new_billing_page")) {
// new code path
}
ctx is the current user: their id, verified email, whether they're an admin, and their workspace ids. The check is synchronous and reads an in-memory snapshot that refreshes every 5 seconds and right after an admin change, so it adds no network call to your request.
A flag that is unknown, not yet loaded or hit an error is off. Signed-out visitors and background jobs with no user only see flags that are on.
What the browser sees
The server decides and enforces every flag. The browser only gets a list of the flags that are on for that person, like {"new_billing_page": true}, and nothing else. The full flag list and its targeting are never sent to anyone who isn't an admin.
Signed-in users get their flags with their session. Signed-out visitors get them from a public config endpoint that is never cached and can only contain flags that are on for everyone. In the web app, a useFlag("key") hook reads whichever applies.
Changing a flag
Admins change flags through an admin API:
| Request | Does |
|---|---|
GET /admin/api/flags | Lists every flag with its state, rules and who last changed it |
GET /admin/api/flags/{key} | One flag and its history, newest first |
PATCH /admin/api/flags/{key} | Sets state, rules (the whole set) and an optional note |
POST /admin/api/flags/{key}/kill | Sets the flag off and keeps its rules |
A bad state, a percentage outside 0 to 100, or targets with nobody listed is rejected. Each change locks the flag, then writes the new state, a history entry and an audit-log entry together, recording who made it and the state and rules before and after.
CLI
flags list
flags get <key>
flags set <key> <state> [--percent N] [--note "..."]
flags target <key> --users a,b --workspaces x,y
flags kill <key> [--note "..."]
Agents
An agent changes flags the same way a person does, with the CLI or the admin API, and only with admin credentials. Customer API keys never get flag access. In Transformer's production system, admin agents also use MCP tools: flags_list, flag_get, flag_set and flag_kill. A hosted Flagger MCP server is in development.
Because unknown flags are off and every change is recorded with a note, an agent can ship a feature dark, turn it on for its own team, and leave a clear trail for the people reviewing its work.
Tests
withFlag(key, on, fn)runsfnwith the flag forced on or off.setFlagOverride(key, on)forces a flag for a whole test file.bothFlagStates(key, fn)runsfnwith the flag off, then on, so both code paths stay tested until the flag is removed.
Good habits
- Off by default. A missing or unknown flag is off, so a deploy never turns on unfinished work by accident.
- Check on the server. The server decides and enforces; the UI only hides what the server already refuses.
- No secrets in flags. Flags hold state and targeting, never keys or tokens.
- Remove when done. Once a feature is on for everyone and stable, delete the flag and the old code path.