Feature flags
Rollouts, targeting, bucketing that is identical in every SDK, and local evaluation on your servers.
Flags are free on every plan. Local evaluation on your servers is unlimited; remote evaluation is fair-use.
Kinds
| Kind | Returns |
|---|---|
| Boolean | true / false |
| Multivariate | a variant key such as control or test, chosen by integer weights |
| Remote config | a JSON payload |
Each flag has a separate configuration per environment (production, preview, development), so you
can turn something on in development without touching production. Every change is written to the project's
annotations, so it shows up on charts.
Production is guarded
Making a production configuration live needs the named flags.production scope. A token or MCP client with
only flags:write can prepare a flag but not ship it.
Targeting
Rules are checked in order; the first rule whose conditions all match decides the result. Conditions can read
person properties, group properties and cohorts, with these operators: exact, in, is_not, not_in,
icontains, not_icontains, regex, not_regex, gt, gte, lt, lte, is_date_before,
is_date_after, semver_eq, semver_gt, semver_gte, semver_lt, semver_lte, is_set, is_not_set.
Bucketing
Rollouts are sticky. The bucket for a subject is:
sha1("<flag_key>.<salt><stickiness value>") → first 15 hex digits → ÷ 0xFFFFFFFFFFFFFFF → [0, 1)
in rollout when bucket < percentage / 100Variants use the same hash with a separate variant salt. The result is bit-identical in every SDK and in
the flag service: a shared set of test vectors (packages/contracts/test-vectors/flag-bucketing.json) runs
in each implementation's test suite.
Stickiness is chosen per flag: distinct_id (default), device_id, person_id (server only) or a group type.
When a device is identified, the service pins the person's bucket to the one their anonymous id already had,
so nobody switches variant just by signing in.
Evaluating flags
import * as atrix from "@atrix.dev/analytics-web";
if (atrix.isEnabled("new-checkout")) {
// …
}
const variant = atrix.getVariant("pricing");Client SDKs load all flags once, cache them next to the identity, and reload on identify, group and
reset. Pass bootstrap: { flags: {…} } to init for values before the first load.
Exposure events
Each flag read sends $feature_flag_called once per flag and value (per session on clients, per user on
servers). Experiments count exposure from these events, so leave them on for any flag behind an experiment.