Flags and kinds
A flag is a named decision your app asks FortressFlag to make: should this device see the new checkout? Which caption goes on the buy button? How many retries before we give up? This page gives you the vocabulary the rest of the docs build on.
Flags live in a project, and have a value per environment
Section titled “Flags live in a project, and have a value per environment”Every flag belongs to a project and is identified by its key — a short lowercase name
like new-checkout. The key is what your code asks for, and it’s immutable after
creation: your shipped apps have the key baked into their code, so renaming it would break
them. You can change a flag’s display name any time; the key is forever.
A flag doesn’t have one value — it has a value per environment. new-checkout can be
on in dev, rolling out in staging, and off in production, all at once. You toggle and
target each environment independently.
The three kinds
Section titled “The three kinds”Every flag has a kind, chosen at creation and immutable after it:
| Kind | Values | Good for |
|---|---|---|
| Boolean | true / false |
Kill switches, feature gates, rollouts |
| String | Named text variants | Copy experiments, algorithm selection, themes |
| Number | Named numeric variants | Limits, timeouts, thresholds |
The kind is immutable for the same reason the key is: your code reads the flag through a
typed call (isEnabled, stringValue, numberValue), and a flag that changed kind under a
shipped app would stop making sense to it.
Variants
Section titled “Variants”String and number flags serve variants — named values like control → "buy-now" and
treatment → "start-free". The names are for you and your dashboard; your users’ devices
only ever receive the value. Variant names never leave the management plane.
Defaults, and the default behind the default
Section titled “Defaults, and the default behind the default”Two different things are called a default, and it’s worth keeping them apart:
- The flag’s default is what an environment serves when no targeting rule matches. For a boolean flag that’s its on/off state; for a multivariate flag it’s the variant you pick as default.
- Your code’s default is the value you pass at the call site —
isEnabled("new-checkout", default: false). This is the bottom of the fallback chain: it’s what your app uses when it has never reached FortressFlag, or when the flag doesn’t exist (or doesn’t have a value to serve) in that environment.
A brand-new multivariate flag with no default variant chosen serves nothing — your code’s default answers until you decide. That’s deliberate: a flag you haven’t finished configuring should behave like a flag that isn’t there.
The lifecycle: archive, restore, purge
Section titled “The lifecycle: archive, restore, purge”Deleting a flag is reversible — up to a point. Archiving removes it from your apps: on every online device the flag stops resolving within about a minute, and your code’s defaults take over. An archived flag can be restored intact within the retention window, or purged permanently after it. The archive list lives inside each project, beside the flags it holds.
If a flag has been sitting untouched for 90 days, the dashboard badges it as stale — a nudge to archive it, never an auto-archive. Flags you’ve forgotten are complexity your codebase is still paying for.
Where to next
Section titled “Where to next”- Targeting — rules, segments, and percentage rollouts
- Environments and projects — where flags live
- How flags reach your app — what a device actually receives