Skip to content

Feature flags

Server-evaluated boolean and multivariate flags with deterministic bucketing, ordered targeting groups, JSON payloads, and one bootstrap call for flags, surveys, and recording config.

Feature flags gate code paths per user — ship dark, roll out gradually, target by person properties, and attach JSON payloads. Evaluation happens server-side: targeting rules and other people’s person properties never reach the client. The browser receives only the resulting values.

Create a flag

A flag has a key, a type — boolean or multivariate (named variants with percentage weights) — and optional targeting groups. Flags are created and managed from the Feature flags page in the app.

Feature flags list with rollout state and targeting

An inactive flag evaluates to false for everyone. That is the entire off switch — no deploy, no cache to chase.

Target and roll out

Bucketing is deterministic: a hash of the flag key and distinct_id maps each person to a fixed position in [0, 1). The same person always lands in the same bucket, across processes and deploys, and raising a rollout percentage only ever adds users — nobody who has the feature loses it because you went from 20% to 40%.

Targeting rules are ordered groups, each with property conditions and a rollout percentage. Groups evaluate in order and the first group whose conditions pass (and whose rollout hash passes) wins. A group-level variant override beats the multivariate distribution — “everyone on the enterprise plan gets test” is one group at the top. A flag with no groups ships to everyone (at 100%).

Condition evaluation is fail-closed: a missing person property fails every operator except the explicit absence checks, and an invalid regex never matches.

Use in code

The SDKs fetch flags through one bootstrap call and read them locally. In the browser, flags load automatically on init:

import { init, azfive } from '@azfive/capture';

init('azfive_pub_…', { host: 'https://analytics.example.com' });

azfive.onFlags(() => {
  if (azfive.isFlagEnabled('new-onboarding')) {
    // show the new flow
  }
  const variant = azfive.getFlag('exp-checkout'); // 'control' | 'test' | false
});

On a server, use a secret events:write key and pass the distinct_id per call:

from azfive_capture import Client

azfive = Client("azfive_…", host="https://analytics.example.com")

variant = azfive.get_flag("user-42", "exp-checkout")  # 'control' | 'test' | False | None
if azfive.is_flag_enabled("user-42", "new-onboarding"):
    ...

The method names follow each language’s convention: getFlag / isFlagEnabled in TypeScript, Kotlin, and Swift; get_flag / is_flag_enabled in Python; GetFlag / IsFlagEnabled in Go.

Under the hood every SDK calls POST /api/v1/decide, which returns flags, flag payloads, active surveys, and session-recording config for one visitor in a single round trip. Browsers authenticate with a public azfive_pub_… token (origin-validated); server SDKs use their secret events:write key, which needs no Origin. The request is {distinct_id, project?, person_properties?} — client-supplied person_properties are merged over the stored person properties, useful for anonymous visitors or properties that haven’t reconciled yet.

Payloads

Any flag value — true or a variant key — can carry a JSON payload: copy, prices, model names, whatever configuration the variant needs. getFlagPayload (get_flag_payload, GetFlagPayload) returns it. Payload reads deliberately fire no exposure event — read the flag first, then its payload.

Exposure and experiments

Reading a flag with getFlag or isFlagEnabled emits an az.flag_called exposure event, deduped so hot code paths don’t flood ingest. getFlagPayload never emits one. Exposure events are what tie flags to experiments: a person enters an experiment at their first exposure to one of its variants, so exposure only fires where a decision was actually used.

Reference

Decide

EndpointPOST /api/v1/decide
AuthPublic azfive_pub_… token (browser, origin-validated) or secret events:write key (server)
Request{distinct_id, project?, person_properties?} — overrides merge over stored properties
Response{flags, flagPayloads, surveys, sessionRecording}
MeteringEvery call is metered; over quota → 429 with {"code": "quota_exceeded"}

Evaluation

RuleBehavior
BucketingHash of flag key + distinct_id → fixed position in [0, 1); monotonic under rollout increases
GroupsEvaluated in order; first passing group wins
Variant overrideGroup-level variant beats the multivariate distribution
No groupsShips to everyone
Inactive flagfalse for everyone

Caches

CacheDuration
Server flag definitions30 s, invalidated on every flag write
SDK decide response60 s per distinct_id

POST /api/v1/decide is the only programmatic surface; creating, targeting, and rolling out flags happens in the app, where deleting a flag is blocked while an experiment references it.