Get started
Concepts
On this page
Flags and types
A flag belongs to a project and has:
- a key (for example
checkout-v2) and a type:boolean,string,numberorjson(any JSON value). Key and type are immutable; - a purpose: release, experiment, operational or permission;
- a lifetime: temporary, or permanent for kill switches and long-lived configuration (the CLI's cleanup refuses permanent flags);
- client exposure: only client-exposed flags are delivered to browser (client) keys.
Flags are not deleted; they are archived (and can be restored). An archived flag is not in the bundle, so SDKs return the caller's fallback for it.
Per environment, every flag has a configuration: enabled, an ordered list of rules, a default value (enabled, no rule matched) and an off value (disabled).
How a flag is evaluated
The same engine runs in the SDKs, the edge, the CLI and the dashboard, so the same configuration, flag key and context always give the same result. Rules are data; no user code runs during evaluation.
- No configuration loaded, or the flag is not in it → the caller's fallback, reason
flag_not_found. - Flag disabled → its
offvalue, reasonoff. - Rules in order; the first rule whose conditions match decides (reason
rule_match). A rule serves a fixed value or a rollout (a percentage getsvalue, the restotherwise). - No rule matched → the flag's
default, reasondefault. - Anything unexpected → the caller's fallback, reason
error. Evaluation never throws.
A rule matches when all or any of its conditions match (a rule without conditions matches every context). Conditions compare one context attribute:
| Operator | Matches when |
|---|---|
eq | Equal (objects and arrays compared as JSON). A missing attribute never matches. |
neq | Present and not equal. |
in / not_in | Equal to one of a list / present and equal to none. The value must be a list. |
contains / starts_with / ends_with | String comparisons; both sides must be strings. |
gt / gte / lt / lte | Numeric comparisons. Numeric strings are converted; anything else never matches. |
exists / not_exists | Attribute present and not null / absent or null. |
segment / not_segment | The context is (not) in the named segment. |
Environments
A project has several environments (new projects: Development, Staging, Production). Each environment has its own draft for every flag, its own published revisions and its own keys: a key reads exactly one environment. Admins configure per environment:
- Protected, require approval with 1 to 5 required approvers, and require scenarios. New Production environments are protected, and on Team and Scale they also require approval and scenarios (required approvals and scenario checks are not available on Explore);
- the daily cap on stored sampled events (see Limits).
Promote configuration (Settings → Environments) copies selected flag configurations from one environment's draft into another's draft. It does not publish.
Draft and published revision
Editing a flag changes the environment's draft. Nothing reaches SDKs until you publish: publishing snapshots the whole draft of the environment (all flags and segments) into an immutable, numbered revision with a checksum. SDKs only ever receive published revisions.
- Every publish names the revision it expects to replace. If the environment moved on in the meantime, the publish is refused (
DESTINATION_CHANGED) instead of overwriting someone else's revision. - A draft identical to the published configuration cannot be published (
NO_CHANGES); values that do not match the flag's type are refused (INVALID_VALUE). - To go back, restore an earlier revision's content into the draft and publish it as a new revision. History is never rewritten.
Changes and approvals
A change groups the flags you intend to modify in one environment with a title, a reason and an optional code reference. It moves through draft → testing → awaiting_approval → approved → published (or closed).
- In an environment that requires approval, publishing is only possible through a change (
CHANGE_REQUIREDotherwise) with at least the required number of valid approvals (APPROVAL_REQUIRED). - Approvers must be admins or owners, and the author of a change cannot approve it (
SELF_APPROVAL). - An approval is bound to the checksum of the environment's draft at the moment it was given. Any later edit to that draft makes it stale; it no longer counts and the change needs a fresh approval. A rejection sends the change back to
draft. - API keys cannot bypass review: an admin key cannot publish an environment that requires approval.
Scenarios
A scenario is a saved check: a flag, a context and the value you expect, for example "a signed-in user in the US gets true". Scenarios run against the draft or the published revision of an environment, in the dashboard or in CI.
- Pass means the actual value deep-equals the expected value (object key order ignored, array order significant, no type coercion). A scenario whose flag is missing (
flag_not_found) or whose evaluation errors never passes, even when it expectsnull. - Scenarios marked include in CI run in CI. In an environment that requires scenarios, every scenario in scope must have passed on the exact draft being published (same checksum); editing the draft requires a new run. In scope are the scenarios of the change's flags, or every CI scenario when publishing without a change.
Segments
A segment is a named, reusable set of conditions (all or any) defined once per project, for example beta-testers or internal-staff. Rules reference it with the segment / not_segment operators. A segment can reference other segments up to four levels deep; a reference to an unknown segment never matches (and not_segment of an unknown segment does not match either). Segments are part of the draft and are published with it.
Rollout bucketing
Percentage rollouts are deterministic: no randomness, no stored assignments. The same identity always lands in the same bucket for the same seed, in every SDK and on every machine.
bucket = (murmur3_x86_32(utf8(nfc(seed + ":" + identity)), 0) mod 10000) / 100 // 0.00 … 99.99
inside = bucket < percentage- Hash: MurmurHash3 x86 32-bit, hash seed 0, over the UTF-8 bytes of the NFC-normalized string. Used for distribution only, never for security.
- Identity: the context attribute named by the rule's
bucketBy, defaultkey. A context without it (missing,nullor empty) is treated as outside the allocation and getsotherwise. - Seed: the rule's
seed, by default the flag key, so allocations of different flags are independent. Give two flags the same seed to allocate the same users to both. - Percentages have two decimals (0–100). Raising the percentage keeps everyone who was already inside; lowering it removes the highest buckets first.
The evaluation result includes bucket and inRollout; the trace explains the decision.
Signed bundles
Each published revision is signed with Ed25519 by NanoFlags before it is distributed. Browser keys receive a separately signed client view that contains only client-exposed flags. The edge serves the signed bytes unchanged, so an SDK (or your own code) can verify that a bundle is exactly what was published for that environment and revision. Right after a publish the edge may briefly serve an unsigned bundle (X-NanoFlags-Signed: false) until signing has caught up.
Details: signature format and verification, and the SDK's verify option.
Observed vs unknown
NanoFlags keeps four kinds of state visibly distinct, and never calls a release "safe":
| State | Meaning | Source |
|---|---|---|
| Draft | What you are editing. Not delivered to SDKs. | The environment's draft |
| Published | The revision the edge serves for the environment. | The environment's published revision pointer |
| Reported / observed | A process using a key of this environment said it had revision N loaded, or a sampled evaluation was received. | SDK service reports and sampled events |
| Unknown | No report. Telemetry may be disabled, blocked, sampled out or dropped. | Absence of signals: proves nothing |