Concepts

Projects, environments, flags, contexts, targeting, rollouts, segments, keys, and how quickly a change reaches your code.

Projects and environments

Your organisation holds projects, usually one per product or codebase. A project holds its flags and segments, and each project has environments, such as development, staging and production. A flag exists once per project, but whether it’s on and who gets what is set separately in each environment, so you can try a change in staging before production sees it.

An environment can be marked critical. production and prod are critical by default; featflags uses that to tell you when a flag has finished its job (see Flag lifecycle). An environment can also require a comment on every change, or an approval before a change goes live: see Approvals and automation.

People have a role in the organisation, or only in some projects or environments: reader sees everything in its scope, writer also changes flags and segments, admin also manages projects, environments, webhooks and members, and owner also manages owners, API tokens and the organisation itself. Roles limited to one environment are on the Business plan.

Flags and variants

A flag has variants: the values it can serve. A boolean flag has two, true and false, but a variant’s value can be any JSON value: a string, a number, or an object of settings. Your code asks for the flag with a fallback, and gets the value of the variant featflags picked for the user, or the fallback when the flag doesn’t exist.

In each environment a flag is on or off. Off serves its off variant to everyone. On, it runs its targeting: individual targets, rules and a default, described below.

A flag is client-visible when browsers and mobile apps may read it. Everything else is server-only, and client keys never see it.

Flag lifecycle

A flag is temporary (the default) when it’s meant to be removed once it’s launched, or permanent, for things like entitlements and settings. featflags gives each flag a status in each environment, from the last 7 days of evaluations:

Status Means
New Created in the last 7 days and not evaluated yet.
Active Evaluated, and served more than one variant or was changed.
Launched Evaluated, served one variant only, and wasn’t changed.
Inactive Not evaluated, and older than 7 days.

A temporary flag that’s launched or inactive in every critical environment, and that no other flag depends on, is ready to archive. With code references set up, featflags also tells you when no code reads it any more: it’s safe to remove.

Archiving a flag takes it out of what SDKs receive, so they return your fallback. Its settings stay, and unarchiving brings it back as it was. Only an archived flag can be deleted.

Contexts

A context is who or what a flag is evaluated for: a user, an organisation, a device. It has a kind (user when you don’t say), a key that identifies it within its kind, and any other attributes you want to target on.

{ "kind": "user", "key": "u-alice", "email": "alice@acme.com", "plan": "pro", "groups": ["qa", "eng"] }

A multi-context carries several kinds at once, so one flag can target both the person and their company:

{ "kind": "multi", "user": { "key": "u-alice", "plan": "pro" }, "org": { "key": "o-acme", "plan": "team" } }

Attribute values are strings, numbers, booleans, or lists of those. Contexts work the way LaunchDarkly’s do, so code and targeting move across unchanged.

Contexts stay where you evaluate them. SDKs evaluate flags in your own process, and nothing about the context is ever sent to featflags.

Targeting

When a flag is on in an environment, featflags decides what a context gets in this order, and the first step that applies wins:

  1. Prerequisites. A flag can depend on others: if a prerequisite flag is off, or serves the context something other than the variant you required, the flag serves its off variant.
  2. Individual targets. Lists of keys that get a given variant, such as your QA team getting on.
  3. Rules, top to bottom. A rule matches when all its conditions do, and serves a variant or a percentage rollout.
  4. The default, a variant or a percentage rollout, for everyone else.

A condition reads one attribute of one context kind and compares it with one or more values; it holds when any value matches.

Operators Compare
is one of strings, numbers or booleans, exactly
contains, starts with, ends with text, case-sensitive
matches a regular expression
greater than, less than (or equal) numbers
before, after dates and times
semver equal, greater, less version numbers, such as 5.2.0
exists whether the context has the attribute at all
is in segment membership of a segment

Any condition can be negated. A context without the attribute never matches a condition on it, even a negated one, so “plan is not free” doesn’t catch contexts with no plan at all; use exists for that. When an attribute is a list, the condition holds if any item matches.

Before a change goes live you can test it: the flag page evaluates any context you type against the targeting as you’ve edited it.

Rollouts

A percentage rollout splits contexts between variants, such as 10% on and 90% off, in steps as fine as 0.001%. Each context’s place in the split comes from a hash of the flag and its key, so the same user always lands in the same place, in every SDK, on every server and device, without featflags storing anything. Growing a variant’s share only ever adds people to it: going from 20% to 50% keeps the first 20%.

A rollout splits by the user’s key unless you pick another kind or attribute, such as the organisation’s key, so everyone in a company sees the same thing. A context without that kind or attribute can’t be placed: a rule with that rollout is skipped, and a default rollout serves the off variant.

A rollout can also have steps: dates when the split changes on its own, such as 10% on Monday and 50% on Thursday. For anything more than that, with approvals and metrics watching each step, use release templates.

Flags imported from LaunchDarkly keep LaunchDarkly’s way of placing users, so everyone stays in the variant they had.

Segments and lists

A segment is an audience you define once in a project and use in any flag’s rules: “beta customers”, “staff”, “EU accounts”. It’s made of:

  • Included keys, who are always in it.
  • Excluded keys, who are never in it, unless they’re also included.
  • Rules for everyone else, in the same conditions as flag rules.

Short lists you type in the app. Long ones, such as every customer on an enterprise contract, you upload as a text file of keys, one per line, with the CLI or the API. Uploads replace the list and go live in the background:

Plan Keys in one uploaded list
Free 10,000
Team 1,000,000
Business 10,000,000

A flag’s individual targets can be uploaded the same way, per variant.

The lists you upload never travel in plain text: SDKs get the keys hashed, and a browser fetches only the small piece of a long list that its own user’s key falls in, never the whole list.

A segment can be private, for lists nobody should be able to enumerate, such as customers under a confidential deal. A browser or app can only check a private segment for a user your backend has issued a token for, which a server SDK makes at sign-in. See your SDK’s page for how.

Keys

SDKs read flags with an environment’s SDK keys. There are two kinds, and every environment starts with one of each:

  • Client keys (pk_…) go in browsers and mobile apps, where anyone can read them. They only reach client-visible flags, and the targeting of those flags is readable too, so keep sensitive rules in server-only flags. A client environment can list the web origins allowed to use it, and the browser SDK serves nothing anywhere else; it’s a guard against mistakes, not a secret, since anyone can still fetch the flags directly.
  • Server keys (sk_…) go in your servers and jobs. They reach every flag and the secrets private segments need, so treat them like a password.

To rotate a key, create another of the same kind, move your apps to it, then revoke the old one or let it expire; Environment → Keys → Rotate does the first and last steps for you, expiring the old key in 7 days. Revoking a leaked key stops it at once: SDKs using it keep the flags they have but get no more updates.

The app and the API use a different credential, API tokens (fft_…), described in the API reference.

How fast changes arrive

You change a flag in the app; SDKs check for changes on their own:

Where Picks up a change
Servers (server keys) within about 5 seconds
Browsers within a minute, and as soon as the tab is shown again or the network comes back
Mobile apps within a minute while in the foreground, and as soon as the app comes back to the foreground or the network returns

SDKs ask a CDN, not featflags itself, so thousands of servers starting at once don’t slow anything down. If featflags or the CDN has a problem, SDKs keep serving the last flags they had, and you can’t change a flag until it’s back. Browsers and apps also keep the last flags on the device, so the next launch starts from them, even offline.

History

Every change records who made it, when, their comment, and a field-by-field diff, and you can restore an earlier version from the project’s history with a preview of what it will do. How long history is kept depends on the plan: 7 days on Free, 1 year on Team and 3 years on Business. Business organisations also get an audit log of everything that happened across the organisation.

Evaluation reasons

Every SDK can tell you why a flag served what it did, alongside the value: the “detail” methods return the variant and a reason.

Reason What happened
target The context is in an individual target list.
rule A rule matched; the detail names it by id.
default Nothing else applied, so the flag served its default.
disabled The flag is off in this environment.
prerequisite_failed A prerequisite flag didn’t serve the variant required; the detail names it.
fallback The default is a rollout that couldn’t place this context, so the off variant was served.
not_found No such flag for this key: your fallback was returned.
type_mismatch The variant’s value isn’t the type you asked for: your fallback was returned.
error The context was invalid, or the flag’s setup couldn’t be evaluated: your fallback was returned.
membership_pending A long list the flag uses was still loading, so it served what it would without that list.

The SDK specification has the exact rules.