Feature Flags
A feature flag lets code reach main and production before the feature is live. You deploy the code off, roll it out when you are ready, and switch it off in seconds if it misbehaves. That is what makes shipping a two-way door: the flag is the way back.
We run flags on GrowthBook for most products. Early-stage products, this one included, use PostHog for flags and session replay together, so the code below is PostHog. The concepts are identical on either platform: a named switch, defaulted off, rolled out gradually.
Every initiative on the story map carries a feature flag field, set at Define. The switch in the code and the plan on the map are the same name. That is the line from what we decided to build to how it turns on.
Naming
Kebab-case, describing the capability, not the ticket. Add a -v1 suffix when you expect to iterate and want the next version to be a clean new flag.
initiative-notes-collab
request-edit-v1
cross-board-initiatives
The name on the initiative must match the key in the code exactly. A few early flags used snake_case; leave those as they are, and name new ones kebab-case. If the switch is permanent (a plan entitlement, a per-org setting), it is configuration, not a feature flag. Do not use a flag as long-lived config.
Using a flag
Default to off. The helpers fail closed: if PostHog is not configured they return false, so an un-flagged environment never accidentally shows unfinished work.
Web (apps/web/src/lib/posthog.ts):
import { useFeatureFlag } from "@/lib/posthog";
const editEnabled = useFeatureFlag("request-edit-v1", false);
if (editEnabled) {
// render the new edit affordance
}
API (apps/api/src/lib/posthog.ts), evaluated per user:
import { isFeatureEnabled } from "../lib/posthog.js";
if (await isFeatureEnabled("request-edit-v1", ctx.user.id)) {
// take the new code path
}
Use getFeatureFlag(flag, userId) instead when the flag is a multivariate string rather than a boolean. Gate the smallest surface that makes the feature visible, so turning the flag off cleanly removes the feature and nothing else.
Retiring a flag
A flag is temporary by definition. Once it has been at 100% and stable for long enough that you trust it, delete it. Leave it in and you accumulate dead branches that every future reader has to reason about.
- Remove the flag check and the old code path in one PR.
- Delete the flag in the flag platform.
- Clear the field on the initiative if the work is done.
A stale flag is quiet debt: it looks like a live switch but controls nothing. Cleaning it up is part of finishing the feature, not a separate chore for later.
Related: Session Replay, Branching & PRs, Releasing & Rollback, Code Review, Define, Launch