← Blog

Feature flag naming conventions that survive a year

A feature flag naming convention needs three parts in a fixed order: scope, feature, and type, so checkout-new-payment-form and search-typo-tolerance-test tell you what they do without opening the file. Skip one of those parts and a flag ages into a mystery.

The classic failure has a name: new-checkout-v2. It sits at 100% for three years with a dead branch behind it, and nobody can say whether there was ever a v1, whether v3 is coming, or who to ask. The name was the first thing to rot, long before the flag itself did.

This post gives the three-part structure, the parts to leave out, and how to catch a bad name before it merges, not a year later when it is load-bearing.

Key Takeaways

  • A feature flag naming convention needs three parts in a fixed order: scope, feature, and type, for example checkout-new-payment-form or search-typo-tolerance-test.
  • Leave out version numbers, dates, and initials. new-checkout-v2 doesn’t say whether v1 still exists or why there’s a v2 at all.
  • A file-based flag format can carry scope and ownership in frontmatter, so the id only has to name the feature and the type.
  • A naming convention nobody checks gets ignored by the third flag. Catch a bad id with a one-line regex check in CI, the same way you’d catch a bad commit message.

What a feature flag naming convention needs

A feature flag naming convention is a fixed structure every flag id follows, so a name alone tells you what a flag gates, where, and how long it should live. The three parts that do that work are scope (what area of the product), feature (what’s changing), and type (release, experiment, ops, or permission, per Martin Fowler’s feature toggle taxonomy).

Put those three parts in the same order every time:

  • checkout-new-payment-form: scope checkout, feature new-payment-form, type release (no suffix needed)
  • search-typo-tolerance-test: scope search, feature typo-tolerance, type test
  • billing-invoice-export-kill: scope billing, feature invoice-export, type kill
  • onboarding-enterprise-sso-beta: scope onboarding, feature enterprise-sso, type beta

A growth PM scanning a list of active flags should be able to tell which ones are theirs and what each one does without pinging an engineer. An engineer reviewing the pull request should be able to tell, from the id alone, whether a rollback is a one-line edit or a conversation with another team first.

Name flags by type, not just by feature

Most naming advice stops at “describe the feature,” which is how you end up with five flags called some variant of new-checkout and no way to tell them apart. The type suffix fixes that, and it maps cleanly onto Fowler’s four categories, covered in full in what feature flags are:

  • Release flags hide unfinished work. No suffix, or -release if you want it explicit: checkout-new-payment-form. Short-lived, deleted once the feature is fully out.
  • Experiment flags split traffic to test a hypothesis. Suffix -test or -exp: pricing-annual-upsell-test. Lives until the numbers answer the question.
  • Ops flags are kill switches. Suffix -kill or -fallback: search-elastic-fallback. Can live indefinitely on purpose, and the suffix says so.
  • Permission flags gate access to a group. Suffix -beta or -access: billing-enterprise-sso-beta. Long-lived by design, encoding a product rule rather than a rollout.

The suffix tells the next person who greps the codebase what to do with it. Leave a -kill flag alone. A bare release flag past its expected life is a cleanup candidate.

Leave dates, versions, and your name out of the id

new-checkout-v2 fails for a specific reason: the version number describes the flag’s history, not what it does. A year later, v2 answers nothing. Was there a v1 that’s still live somewhere? Is a v3 planned? Versioning a flag name borrows from semantic versioning, but semver versions a public contract that changes on purpose. A flag is the opposite: it’s supposed to be temporary, and baking a version into the name just encodes how many times someone was afraid to delete the old one.

The same goes for dates (checkout-jan-fix) and initials (sam-pricing-test). A date tells you when a flag was created, not what it does or whether it’s still needed, and it’s wrong the moment the flag outlives the sprint it was named for. An owner’s initials in the id duplicate information that belongs in an owner field, and they’re stale the day that person changes teams.

The fix in each case is the same: if the information isn’t the scope, the feature, or the type, it doesn’t belong in the id. It belongs somewhere else in the file.

Let the file carry what the name shouldn’t

A flag id gets overloaded when it’s the only place to put information. Give owner, scope, and status their own fields, and the id stops having to carry them. That’s most of what breaks a naming convention under pressure.

In dif, every flag is a Markdown file with frontmatter that already separates these concerns:

---
id: new-payment-form
status: active
owner: billing@acme.com
surface: checkout
created: 2026-03-14
---

## Brief

Replace the Stripe Elements checkout with the new embedded form.

The surface field carries scope, owner carries who to ask, and created carries the date, so none of them need to live in the id. That leaves the id free to do one job: name the feature and the type, which is also what dif validate checks against, confirming the owner is a real email and the surface exists before the flag can merge. The .md format reference covers every field.

Enforce the convention before it merges

A naming convention that lives in a wiki page gets followed for the first three flags and ignored by the fourth. The fix is the same one that works for commit messages or lint rules: check it in CI, not in someone’s memory.

A naming pattern is a one-line regex check against every flag id touched in a pull request:

# fails the build if an id doesn't match scope-feature[-type]
grep -rhoE '^id: .*' dif/*/active/*.md \
  | grep -vE '^id: [a-z0-9]+(-[a-z0-9]+)+$' && exit 1

Run that alongside dif validate in CI, and a flag with a bad id fails the pull request the same way a flag with a bad owner email does. The CLI reference covers wiring custom checks into the same pipeline.

That’s a small amount of friction for a real payoff. A year from now, ls dif/*/active/ is a list of names you can read, not a list you have to open one by one to understand.

A starter naming convention

If you’re starting from nothing, this structure covers most teams without a meeting:

checkout-new-payment-form        (release)
pricing-annual-upsell-test       (experiment)
search-elastic-fallback-kill     (ops)
billing-enterprise-sso-beta      (permission)

Pick lowercase, hyphen-separated words for the scope and feature. Keep the type suffix to a short, fixed list your team agrees on. Leave version numbers, dates, and names for the frontmatter or the commit history, where they belong.

Feature flag naming convention: what to keep and what to cut

A feature flag naming convention that survives a year has three parts in a fixed order: scope, feature, type. It leaves out anything that changes on its own: versions, dates, owners. And it gets checked by something other than memory. The flags that rot are rarely the ones with a bad idea behind them. They’re the ones nobody could name well enough to question.

Start with the four-type suffix list above, put owner and scope in the file instead of the id, and add a one-line check in CI before the first bad name merges. If you want the file format and the validator that checks it for free, install the CLI with npm install -g @dif.sh/cli and run dif init. The feature flags page covers the rest of the workflow.

FAQ

What is a good feature flag naming convention? A fixed structure of scope, feature, and type, in that order, so a name like checkout-new-payment-form tells you what the flag gates without opening the file. Pick a short, agreed-on list of type suffixes (release, test, kill, beta) and apply it consistently.

Should a feature flag name include a version number? No. A version number describes the flag’s history, not what it does, and it goes stale the moment someone asks whether there’s a v1 or v3. If a flag is being replaced, name the replacement for what it does and delete the old one.

How long should a feature flag name be? Long enough to name the scope and the feature clearly, usually two to five hyphenated words. A name that needs an abbreviation to stay short is usually missing information that belongs in the file instead, like an owner or a surface field.

Should a feature flag name include the owner’s initials? No. Put the owner in a dedicated field, not the name. An id with initials goes stale when that person changes roles, and it duplicates information a reviewer can get from the file or from git blame.

How do you enforce a feature flag naming convention? Check it in CI with a regex against the flag id, the same way you’d lint a commit message. A convention that only lives in a wiki page gets followed for the first few flags and ignored after that.

Is there a standard feature flag naming convention? Not an industry-wide one, but Martin Fowler’s feature toggle categories (release, experiment, ops, permission) give you the type half of the structure, and most teams land on some version of scope-feature-type once they’ve been burned by a few unreadable flag names.