Skip to content
NanoFlags
Start free

Get started

Concepts

The model behind NanoFlags: what you edit, what gets published, what SDKs evaluate, and what the dashboard can and cannot know about your running services.

Flags and types

A flag belongs to a project and has:

  • a key (for example checkout-v2) and a type: boolean, string, number or json (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.

  1. No configuration loaded, or the flag is not in it → the caller's fallback, reason flag_not_found.
  2. Flag disabled → its off value, reason off.
  3. Rules in order; the first rule whose conditions match decides (reason rule_match). A rule serves a fixed value or a rollout (a percentage gets value, the rest otherwise).
  4. No rule matched → the flag's default, reason default.
  5. 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:

OperatorMatches when
eqEqual (objects and arrays compared as JSON). A missing attribute never matches.
neqPresent and not equal.
in / not_inEqual to one of a list / present and equal to none. The value must be a list.
contains / starts_with / ends_withString comparisons; both sides must be strings.
gt / gte / lt / lteNumeric comparisons. Numeric strings are converted; anything else never matches.
exists / not_existsAttribute present and not null / absent or null.
segment / not_segmentThe 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_REQUIRED otherwise) 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 expects null.
  • 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
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, default key. A context without it (missing, null or empty) is treated as outside the allocation and gets otherwise.
  • 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":

StateMeaningSource
DraftWhat you are editing. Not delivered to SDKs.The environment's draft
PublishedThe revision the edge serves for the environment.The environment's published revision pointer
Reported / observedA 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
UnknownNo report. Telemetry may be disabled, blocked, sampled out or dropped.Absence of signals: proves nothing
A report proves that one process had a revision when it reported. It does not prove that every instance received it, that the service is healthy, or that users saw the feature. See what a reported signal proves.

Further reading

SDK reference · Edge HTTP API · Security