Six months from now, someone who never wrote the code will open your flag list and try to work out what newCheckout does. Is it still rolling out? Was it an experiment that already picked a winner? Is it safe to delete, or is it a permanent gate holding up billing? The name answers none of those questions, so they will ask in Slack, and if nobody remembers, the flag stays. That is how a flag list turns into a graveyard nobody trusts.
The name is the one part of a flag that every tool, teammate, and script sees. It shows up in code review diffs, in the dashboard, in audit-log lines, in grep output, and in the SDK call itself. Pete Hodgson’s canonical treatment of feature toggles frames the stakes plainly: toggles are “inventory which comes with a carrying cost,” and teams should “keep that inventory as low as possible” (martinfowler.com). A naming convention is the cheapest tool you have for keeping that inventory legible while it exists, and for making it obvious which items are safe to throw away.
Key Takeaways
- A flag key is a record you query, not a label you read: it should be greppable, sortable, and self-describing to someone who has never seen the code.
- The convention most teams converge on encodes four things in the key: type, area, feature, and a date or owner (
release_checkout_v3_2026q3).- The type prefix (
release,experiment,ops,perm) does most of the work, because it tells triage what is removable without any other context.- Enforce the rule with a regex in CI, not a wiki page nobody reads at creation time. The regex is the real spec.
- You cannot safely mass-rename live keys. Migrate opportunistically with a two-key cutover; version the convention rather than retro-applying it.
1. A flag key is a record you query, not a label you read
The mistake behind most bad flag names is treating the key like a variable name: something short and readable for the person writing this one line of code today. But the key outlives that moment. It becomes a row in a database, a string in a git log -S search, a filter in the dashboard, and an entry in every audit-log line that records a flip.
That means the key has readers who are not you and not now. A good key answers their questions without a lookup:
- A teammate scanning the flag list wants to know, at a glance, what this gates and whether it is still live.
- An engineer running an incident triage wants to
grepthe codebase for every flag in one subsystem, which only works if area is encoded in the name. - A cleanup pass wants to sort flags by age and type to find removable ones, which only works if type and a date are in the key.
- The next person to reuse a bit needs the name to scream “this is permanent, do not touch” or “this shipped in 2024, it is dead.” The absence of that signal is the specific failure that took down Knight Capital: a bit whose original purpose was unreadable from the name, reused years later.
So the design goal is not “short and pretty.” It is “legible to a stranger, greppable by tooling, sortable by lifecycle.” Everything below follows from that.
2. The anatomy of a good flag key
The structure most teams settle on packs four questions into one string:
release_checkout_v3_2026q3Each segment answers one of the four things a future reader asks:
Read left to right, release_checkout_v3_2026q3 says: this is a release flag (so it is meant to be removed once rolled out), it gates the checkout subsystem, it is the v3 iteration, and it landed in 2026 Q3. A stranger can triage it without asking anyone. Compare that to newCheckout, which is stale the day after it ships and carries no lifecycle hint at all.
The public reference conventions land in the same place. Statsig’s naming-convention glossary recommends a “prefix or suffix indicating the flag type (e.g., release, experiment, ops, or permission)” plus a feature-area segment (statsig.com). The ordering varies (some teams put the type last), but the four ingredients are stable.
One product note: in Featureflip the key is auto-generated from the flag’s human name (so “New Checkout Flow” becomes new-checkout-flow), and you can edit it before saving. That auto-generated default is a starting point, not the convention. Edit it to the structured form at creation time, because renaming later is expensive (section 7). The creating-flags guide covers where that field lives in the dashboard.
3. Start with the type: four lifecycle prefixes
If you adopt only one segment, adopt the type prefix. It is the segment that makes cleanup possible, because it separates “should eventually be deleted” from “intentionally permanent” without anyone re-deriving the flag’s purpose.
Four types cover almost every flag:
| Prefix | Lifecycle | Removable? | Example |
|---|---|---|---|
release_ | Ships a change behind a percentage rollout, removed once at 100% | Yes, within weeks of full rollout | release_search_ranking_2026q3 |
experiment_ | A/B or multivariate test, removed when the experiment ends | Yes, plus a short stabilization window | experiment_pricing_page_layout_2026q3 |
ops_ | Operational kill switch or circuit breaker, flipped during incidents | No, lives as long as the dependency it guards | ops_killswitch_recommendations |
perm_ | Permanent gate: plan-tier entitlements, regional rules, per-tenant overrides | No, permanent by design | perm_entitlement_pro_plan_exports |
The payoff shows up during triage. When you audit the flag list, the two removable types (release_, experiment_) are your cleanup queue, and the two permanent types (ops_, perm_) are the flags you deliberately skip. This mirrors the four buckets in the flag cleanup playbook: a good type prefix means triage is a filter, not a meeting.
Tagging the type at creation is also the first of the eight rules in the broader feature flag best practices reference. Naming is where that tag becomes durable, because a tag can be edited away but a key that starts with perm_ announces its intent every time anyone reads it.
4. Delimiter, casing, and length: the boring decisions that bite
These choices feel trivial, so teams skip agreeing on them, and then every flag list is a mix of newCheckout, new-checkout, and new_checkout that no single grep catches. Decide once:
- Pick one delimiter and one case. Lowercase with a single separator, applied everywhere. Public conventions lean toward “lowercase letters and underscores” (statsig.com); Featureflip’s auto-generated keys use kebab-case (
new-checkout-flow). Either works. What does not work is mixing them, because inconsistent delimiters break greppability, which is half the reason to have a convention. Whatever you choose, encode it in the CI check (section 5) so drift cannot start. - No spaces, no camelCase, no uppercase. Keys travel through URLs, environment variables, log labels, and shell scripts. Keep them to
[a-z0-9]plus your delimiter and you never have to quote or escape them. - Budget the length. Four segments of one or two words each is the sweet spot.
release_checkout_v3_2026q3is long enough to be self-describing and short enough to type. If a key needs five or six segments to be clear, that is usually a sign the area segment is doing two jobs and the subsystem should be split. - Encode time, not people. A quarter (
2026q3) still means something after the author leaves; an owner initial (_jdoe) becomes a dead reference the day they change teams. If you want ownership, put it in the flag’s owner field or tags, not the immutable key. Reserve the last segment for a date you can sort on.
None of this is exciting, which is exactly why it needs to be written down as a rule a machine enforces rather than a habit each person half-remembers.
5. Enforce the convention in CI, not on a wiki
A naming convention documented on a wiki page is worth almost nothing, because nobody reads the wiki at the moment they create a flag. A convention enforced by a check that fails the pull request is worth a lot, because it catches the non-conforming name before it ever reaches production. Statsig’s guidance says the same thing in operational terms: “implement validation,” “automate flag creation with templates that follow the naming convention,” and “conduct code reviews to catch and correct any deviations” (statsig.com).
The regex is the real specification. Write the convention down as a pattern and the rest is plumbing:
// scripts/check-flag-names.js: fail CI on any non-conforming flag key.const FLAG_KEY_PATTERN = /^(release|experiment|ops|perm)_[a-z0-9]+_[a-z0-9-]+_(20\d{2}q[1-4]|[a-z0-9-]+)$/;
// Source the keys however you like: a diff scan, a grep for your SDK's// evaluation call, or your platform's flag-list API.const newFlagKeys = collectNewFlagKeys();const offenders = newFlagKeys.filter((key) => !FLAG_KEY_PATTERN.test(key));
if (offenders.length > 0) { console.error('Non-conforming flag keys:\n ' + offenders.join('\n ')); console.error('\nExpected: <type>_<area>_<feature>_<quarter>, e.g. release_checkout_v3_2026q3'); process.exit(1);}Three layers make it stick, cheapest first:
- A pre-commit hook that runs the regex against keys added in the diff. It gives the author feedback in seconds, before CI even starts.
- The CI check above, so the rule holds even when someone skips the hook. This is the gate that actually enforces it.
- A creation-time check against your platform’s API. The strongest version validates the key when the flag is created, not just when code referencing it lands. If your pipeline creates flags through the Management API, it can apply the same regex at the moment the key comes into existence, the earliest point enforcement is possible. Featureflip’s flag list is also queryable, so a scheduled job can flag any key that does not match the pattern and surface it as flag debt before it spreads.
Keep the pattern in one file that both the hook and CI import, so the rule has exactly one definition. When the convention changes, you change the regex, and every layer picks it up.
6. Naming by team size
The right amount of structure scales with how many people share the flag list. Over-engineering a solo project’s keys is as wasteful as under-structuring an org’s.
- Solo or a single small app. You are the only reader, and you remember context, so two segments are plenty:
release_dark_mode,ops_killswitch_email. Keep the type prefix (future-you still needs to know what is removable), drop the area if there is effectively one area. - A small team (roughly 2 to 15 engineers). Now readers exist who did not write the flag, so the area segment starts earning its place:
release_checkout_v3_2026q3. This is the sweet spot the four-segment template was designed for. - Multiple teams in one project. Add a team or domain namespace as the leading segment so ownership is visible and one team can filter to only its flags:
payments_release_checkout_v3_2026q3. Resist the urge to encode the environment in the key. Environments are a dimension the platform already models (the same key is evaluated per environment); bakingprodinto the name means you cannot promote the same flag across environments without renaming it.
The value of the convention grows with the team, because the whole point is legibility to people who were not in the room. This is illustrative, but the shape holds: the more of the four segments a key carries, the more of the flag list a new engineer can classify without asking anyone.
7. Migrating off bad names without breaking production
The hardest question about naming conventions is never how to name new flags. It is what to do about the hundreds of badly-named flags you already have. The honest answer is that you cannot safely mass-rename them, and pretending otherwise is how you cause an outage.
A flag key is not a label you can edit freely. It is the identifier your code passes to the SDK and the string the platform matches on to serve a variation. Rename the key in the dashboard without changing every call site, and the SDK starts evaluating a key that no longer exists, so it returns the default value for every user. That is a silent behavior change across production, which is exactly the class of incident the two-PR discipline in the cleanup playbook exists to prevent.
So migrate deliberately, not in a bulk sweep:
- Freeze the convention going forward. Turn on the CI check from section 5 so no new non-conforming keys are created. The debt stops growing immediately, which is most of the win.
- Rename opportunistically, with a two-key cutover. When you next touch a badly-named flag, create the new correctly-named key, point the code at it, verify the new key is serving correctly, then retire the old one. It is the same create-new, cut-over, delete-old pattern you would use for any identifier that lives in both code and a data store. Never flip the key out from under running code.
- Never bulk-rename live keys. A script that renames every key in the platform will orphan every call site at once. If you must normalize a large batch, do it flag by flag through the cutover above, and only for flags worth the effort. Most bad names attached to soon-to-be-removed release flags are cheaper to simply delete on schedule than to rename.
The pragmatic path is almost always: enforce the convention on new flags, rename the permanent ones you will keep for years, and let the removable ones age out through normal cleanup. You do not need a pristine flag list. You need one where every flag created from today forward is legible, and where the long-lived flags are worth reading.
8. When the convention itself changes
Conventions evolve. You will add a fifth type, or move the team namespace to the front, or decide quarters should be 2026-q3 instead of 2026q3. The instinct is to retro-apply the new rule to every existing flag. Resist it, for the same reason as section 7: renaming live keys is a production change, and doing it in bulk for cosmetic consistency is all risk and no reward.
Version the convention instead of rewriting history:
- Bump the regex, not the back catalog. The CI pattern is the current spec. Change it, and every flag created from that point conforms. Older flags are grandfathered. They were valid under the previous rule and they still evaluate correctly.
- Let cleanup do the normalization. Removable flags carrying the old convention will age out of the system on their own cadence. Within a couple of quarters, most of the live flag list already follows the new rule without a single rename.
- Only hand-migrate the permanent flags that matter. If a long-lived
perm_orops_flag genuinely reads wrong under the new convention, migrate that one flag with the two-key cutover. That is a handful of flags, not the whole list.
Treat the convention like any other piece of infrastructure config: the current version applies going forward, and you migrate the past only where the cost is justified.
The shorter version
A flag key is a record other people and other tools query, not a label you read once while writing a line of code. Structure it so a stranger can triage it: encode the type, the area, the feature, and a date (release_checkout_v3_2026q3). Of the four segments, the type prefix earns its keep first, because it tells cleanup what is removable without anyone re-deriving the flag’s purpose.
Pick one delimiter and one case, encode time rather than people, and write the whole rule down as a regex that fails the pull request. A wiki page is a suggestion; a CI check is the convention. Scale the amount of structure to the size of the team, because the payoff is legibility to people who were not in the room.
And be realistic about the flags you already have. You cannot safely mass-rename live keys, so freeze the convention on new flags, rename the permanent ones worth keeping, and let the removable ones age out through cleanup. When the convention itself changes, bump the regex and grandfather the past rather than rewriting it. Good naming is not a one-time cleanup. It is a rule you enforce at creation, every time, so the flag list stays worth reading.
Naming sits inside the broader lifecycle: tagging types at creation, testing both states, rolling out progressively, and cleaning up on a cadence. For the full set, see the eight-rule best-practices guide and the anti-patterns that cause real incidents.
Frequently asked questions
What is a good feature flag naming convention?
Encode four things in the key: the type (lifecycle), the area (subsystem), the feature (the specific change), and a date so cleanup can sort by age. The template most teams converge on is <type>_<area>_<feature>_<quarter>, for example release_checkout_v3_2026q3. Public references land in the same place, recommending a type prefix such as release, experiment, ops, or permission plus a feature-area segment (statsig.com). The type prefix is the most valuable segment because it tells you what is safe to remove.
Should feature flag keys use kebab-case or snake_case?
Either works. What matters is picking one and applying it everywhere, because inconsistent delimiters break the greppability that is half the reason to have a convention. Some conventions recommend lowercase with underscores; Featureflip auto-generates kebab-case keys from the flag name. Choose one, keep keys to lowercase letters, digits, and your single separator (so they travel safely through URLs, env vars, and shell scripts), and encode the choice in your CI check so drift cannot start.
How do you enforce a flag naming convention?
Write the convention as a regex and run it in continuous integration so a non-conforming key fails the pull request. A wiki page does not work, because nobody reads it at the moment they create a flag. Add a pre-commit hook for fast local feedback, the CI check as the real gate, and ideally a creation-time check against your platform’s flag-list API so keys are validated the moment a flag is created. Keep the pattern in one shared file so the rule has exactly one definition.
Can you rename a feature flag key safely?
Not in bulk. The key is the identifier your code passes to the SDK, so renaming it in the platform without updating every call site makes the SDK evaluate a key that no longer exists and return the default value for every user, silently. Migrate one flag at a time with a two-key cutover: create the new key, point code at it, verify it serves correctly, then retire the old key. For soon-to-be-removed flags it is usually cheaper to delete them on schedule than to rename them.
Should the environment be part of the flag name?
No. The environment is a dimension the platform already models, so the same key is evaluated separately per environment. Baking prod or staging into the key means you cannot promote the same flag across environments without renaming it, which reintroduces the risky rename problem. Keep the key environment-agnostic and let the platform handle per-environment configuration.
Featureflip treats flag keys, owners, types, and last-evaluated timestamps as first-class in the dashboard, so a naming convention becomes something you can query and enforce rather than tribal knowledge on a wiki. If you want to see how that plays out on your own flags, start with the Solo plan. It is free forever for one project.