Chapter 01

Feature Flags 101

A flag is a question your code asks PostHog: should this person get this feature? PostHog answers depending on the type of flag:

  • Boolean flags: true or false
  • Multivariate flags: a variant, such as control or test

Any flag can also carry a JSON payload, which you read with getFeatureFlagPayload(). See Payloads.

You change the flag's configuration in PostHog, and the code stays the same.

Deploy is not release

Deployed code is expensive to revert. You can separate shipping that code from giving users access to it.

A feature flag splits deployment and release. You deploy new-checkout turned off, and the new code sits in production doing nothing. Releasing it is a change in PostHog, and so is taking it back: you decide who gets it, how many, and for how long.

The check itself is a few lines. On the server, it runs once at the top of the request, with the signed-in user's ID:

JavaScript
const flags = await posthog.evaluateFlags(user.id)
if (flags.isEnabled(NEW_CHECKOUT_FLAG)) {
return renderNewCheckout()
}
return renderCheckout()

Evaluate once, where the request comes in, and pass the result down. Each extra evaluation deeper in the stack is another chance for the identity or configuration to change and, when the server sends evaluations to PostHog, another network request.

Three inputs, one stable answer

PostHog evaluates a flag from three inputs:

  • The flag's configuration: its release conditions, rollout percentage, and variants, which you set in PostHog.
  • Identity: the person's distinct ID, which your code passes in.
  • Properties: facts about the person or their company, like their plan or country, that the release conditions filter on.

For the rollout percentage, PostHog hashes the flag key and the distinct ID (or, for a group flag, the group key) into a number between 0 and 1. If that number is under the rollout percentage, the flag is enabled:

InputValue
Hash of new-checkout and user-420.19
Rollout percentage50%
Resulttrue, because 0.19 is under 0.50

The same inputs produce the same result on every check, so when a flag gives someone the "wrong" answer, look for what changed: the flag's configuration or one of the inputs. A common cause is the distinct ID: a browser that checks a flag before identify() runs is asking about an anonymous visitor, not your user. Once you know who the user is, call identify() before checking the flag.

Decide what happens before the answer arrives

In the browser, flags load asynchronously, and until they arrive getFeatureFlag() returns undefined, not false. Decide what your code does in that gap, and make it the behavior you already trust:

JavaScript
const showNewCheckout = posthog.getFeatureFlag(NEW_CHECKOUT_FLAG) ?? false

Whatever the flag returns, or if it returns nothing, your app must keep working. A broken flag must never take down production.

You can also wait for flags with posthog.onFeatureFlags(), or send the server's answer along with the page so the gap never opens, which the next page covers.

Flag checks record exposure

When the SDK checks a flag, it captures a $feature_flag_called event with the flag key and the value the person got, and the SDK drops repeats. That event is how PostHog knows who saw what: Experiments counts exposures from it, and it's one signal for telling a flag nobody checks anymore from one that's still in use, as cleanup explains.