The flag was called enable_new_thing. It had been in the codebase for 14 months. Three engineers had worked around it. Nobody knew what "new thing" referred to, whether the flag was still evaluated, or what would break if it were removed. So they left it alone, and it stayed for another year.
This scenario plays out in every engineering organization that uses feature flags without naming conventions. The flag itself is not the problem -- the name is. A descriptive, structured name would have told any engineer exactly what the flag controlled, when it was created, who owned it, and whether it was safe to remove. Instead, enable_new_thing became another entry in the flag graveyard.
Naming conventions are the highest-leverage, lowest-effort improvement you can make to your feature flag hygiene. They cost nothing to implement, require no tooling changes, and immediately improve every flag created from the moment the convention is adopted. Yet most teams either have no convention at all or have one so vague that it provides no meaningful guidance.
This article provides a complete, opinionated naming convention framework that you can adopt today. It covers the structure of a good flag name, prefix systems for flag types, how to encode metadata like dates and ticket references, anti-patterns to avoid, cross-language considerations, and how naming conventions affect automated detection and cleanup tools.
Why flag names matter more than you think
Flag names are not just identifiers. They are the primary interface between a flag and every engineer who encounters it. When a developer is debugging production at 2 AM and sees a flag in a code path, the flag name is the first -- and often only -- piece of context they have.
What a name must communicate in under 3 seconds:
| Information | Why It Matters |
|---|---|
| What the flag controls | So the engineer can assess relevance to the current problem |
| Why the flag exists | So they know if it is a release gate, an experiment, or a kill switch |
| When it was created | So they can judge whether it is stale |
| Who is responsible | So they know who to contact (if encoded or searchable) |
| Whether it is safe to remove | So cleanup decisions can be made quickly |
A name like release_unified_checkout_2025q4 communicates all five in a single string. A name like new_checkout communicates one, vaguely.
The cost of bad names
In our experience, the patterns are consistent when flag names lack structure:
- Significant context-switching overhead every time an engineer encounters an unfamiliar flag during debugging -- they have to stop what they are doing and investigate
- Much longer cleanup time for flags with ambiguous names compared to well-named flags
- A large portion of stale flags in a typical codebase have names that provide no indication of their purpose or age
- Most engineers avoid flag removal when they cannot determine the flag's purpose from its name alone
Bad names do not just slow down cleanup -- they actively prevent it. Engineers who cannot determine a flag's purpose from its name will not remove it. The risk of breaking something unknown outweighs the benefit of removing one flag.
The anatomy of a good flag name
A well-structured flag name has three to four components that follow a consistent pattern. The pattern should be readable by humans and parseable by tools.
Recommended format:
{type}_{feature}_{context}_{date}
| Component | Required | Purpose | Examples |
|---|---|---|---|
| Type | Yes | Indicates the flag's purpose and expected lifetime | release, experiment, ops, perm, migration |
| Feature | Yes | Describes what the flag controls | unified_checkout, search_reindex, billing_v3 |
| Context | Optional | Disambiguates when needed (platform, region, team) | mobile, eu, ios, api |
| Date | Recommended | Encodes when the flag was created | 2025q4, 202506, 2025 |
Separator convention: Use underscores (_) to separate components and hyphens (-) within components. This creates a clear visual hierarchy:
release_payment-processing_api_2025q4
^ ^ ^ ^
type feature ctx date
Some flag management platforms have constraints on allowed characters. LaunchDarkly supports periods, hyphens, and underscores. Unleash supports most characters but recommends kebab-case. Split.io supports alphanumerics, hyphens, underscores, and periods. Choose separators that work with your platform, but maintain the component structure regardless of the specific characters used.
Type prefixes: Making intent obvious
The type prefix is the single most valuable component of a flag name. It tells the reader what kind of flag this is, which directly implies its expected lifetime and removal urgency.
Standard type prefixes
| Prefix | Full Type | Expected Lifetime | Removal Urgency | Description |
|---|---|---|---|---|
release_ | Release flag | 2-12 weeks | High | Controls gradual rollout of a new feature |
experiment_ | Experiment flag | 2-6 weeks | High | Controls A/B test or multivariate experiment |
ops_ | Operational flag | Permanent (reviewed quarterly) | Low | Kill switch or circuit breaker for operational safety |
perm_ | Permission flag | 3-6 months | Medium | Controls access for specific user segments or tiers |
migration_ | Migration flag | Duration of migration | Medium | Controls traffic during system migration |
hotfix_ | Hotfix flag | 1-4 weeks | Critical | Temporary fix with a planned permanent solution |
Why six types, not two? Many teams start with just "feature flag" and "kill switch." This binary classification obscures important differences between a release rollout (remove in weeks), an experiment (remove after analysis), and a migration (remove after cutover). Each type has different lifecycle expectations, and the prefix makes those expectations explicit.
Type prefix in practice
Consider how type prefixes change the way an engineer reads these flags:
# Without type prefix -- what are these?
checkout_flow = client.get_flag("new_checkout")
pricing = client.get_flag("pricing_test")
payments = client.get_flag("disable_payments")
# With type prefix -- immediately clear
checkout_flow = client.get_flag("release_new_checkout_2025q4")
pricing = client.get_flag("experiment_pricing_page_variant_b")
payments = client.get_flag("ops_disable_payment_processing")
The second set requires zero additional context. An engineer seeing experiment_pricing_page_variant_b knows this is a temporary experiment flag that should be removed once the experiment concludes. An engineer seeing ops_disable_payment_processing knows this is a permanent operational switch that should not be removed without careful consideration.
Encoding dates and ticket numbers
Dates and ticket references in flag names provide two critical pieces of information: when the flag was created and where to find the context behind it.
Date encoding strategies
| Strategy | Format | Example | Pros | Cons |
|---|---|---|---|---|
| Quarter | YYYYqN | release_checkout_2025q4 | Easy to scan for staleness | Imprecise (3-month window) |
| Month | YYYYMM | release_checkout_202510 | Good balance of precision and readability | Slightly longer names |
| Sprint | sprint_NN | release_checkout_sprint_42 | Aligns with team cadence | Meaningless outside the team |
| None | -- | release_checkout | Shortest names | No age signal in the name |
Recommended: Use quarter format (YYYYqN) for most teams. It provides enough precision to identify stale flags at a glance without making names excessively long. A flag with 2024q1 in its name in Q4 2025 is immediately suspicious -- no lookup required.
For teams with shorter iteration cycles or strict expiration policies, month format (YYYYMM) provides additional precision.
Ticket number encoding
Including a ticket reference in the flag name creates a direct link to the context behind the flag:
release_checkout_redesign_PROJ-1234_2025q4
Pros:
- Instant traceability to the original feature request or story
- Makes it trivial to find the acceptance criteria and rollout plan
- Helps cleanup tools generate links to relevant tickets
Cons:
- Makes names significantly longer
- Ticket systems change (JIRA to Linear, for instance)
- Ticket numbers are meaningless without the system
Recommendation: Do not embed ticket numbers in flag names. Instead, store the ticket reference as metadata in your flag management platform. The flag name should be human-readable on its own; the ticket reference should be one click away, not crammed into the identifier.
Anti-patterns: Names that guarantee technical debt
Some naming patterns are so common and so harmful that they deserve explicit prohibition. If you see these patterns in your codebase, they are reliable indicators of flags that will never be cleaned up.
The "temp" prefix
temp_fix_api_timeout
temp_disable_cache
temp_workaround_auth_bug
Why it fails: "Temporary" is not a type -- it is a wish. Every flag is temporary in theory. The temp_ prefix provides no information about what the flag does, how long it should exist, or what "temporary" means in practice. Flags prefixed with temp_ have the longest average lifespan in most codebases because the name itself admits there is no plan.
Fix: Replace temp_ with hotfix_ and include a date: hotfix_api_timeout_fallback_2025q4. The hotfix_ type has a defined maximum lifespan (1-4 weeks), creating accountability that temp_ lacks.
The "test" prefix
test_new_feature
test_flag_123
test_johns_experiment
Why it fails: These names suggest the flag was created for local testing or development and was never intended to be permanent. Yet here it is in production, because nobody distinguishes between a test flag and a feature flag in CI/CD pipelines. If it gets deployed, it stays.
Fix: Test flags should be confined to test environments via configuration, not via naming. If a flag exists in production code, it is a production flag and should be named accordingly.
The version suffix
checkout_v2
search_v3
billing_v2_final
billing_v2_final_2
Why it fails: Version numbers create an infinite naming chain. When checkout_v2 is the "new" checkout, checkout_v3 becomes the new "new" checkout, and checkout_v2 is now the "old new" checkout. Neither name describes what changed. The progression from billing_v2_final to billing_v2_final_2 is a reliable sign that the naming system has broken down entirely.
Fix: Describe the change, not the version: release_unified_checkout_2025q4 instead of checkout_v2. When the next checkout redesign happens, it gets its own descriptive name: release_checkout_single_page_2026q2.
The boolean-verb prefix
enable_dark_mode
disable_legacy_search
is_new_user_flow
should_show_banner
Why it fails: These names describe the flag's evaluation result, not its purpose. enable_dark_mode tells you what happens when the flag is true, but not why the flag exists, what type it is, or when it should be removed. Additionally, double negatives become common: what does disable_legacy_search = false mean? Is legacy search enabled or disabled?
Fix: Drop the verb prefix and use a type prefix instead: release_dark_mode_2025q4. The flag management platform handles the boolean logic; the name should describe the feature and context.
The personal name
johns_feature_flag
sarahs_experiment
dev_alex_new_thing
Why it fails: People leave companies. People change teams. When John leaves and his flag remains, johns_feature_flag becomes an orphan with no description and no owner. The name provides zero information about what the flag does.
Fix: Names should describe features, not people. Ownership is tracked as metadata, not encoded in the flag name.
Naming across languages and platforms
Flag names exist in code across your entire stack. A single flag might be referenced in Go backend services, TypeScript frontend applications, Python data pipelines, and Kotlin mobile apps. The naming convention must work across all of them.
Cross-language considerations
| Consideration | Challenge | Solution |
|---|---|---|
| Case conventions | Go uses camelCase for variables, Python uses snake_case, CSS uses kebab-case | Use snake_case in the flag name itself; map to language-specific variable names in wrapper code |
| String length | Some platforms limit flag key length (LaunchDarkly: 256 chars, Split: 250 chars) | Keep flag names under 80 characters to leave room for platform-specific formatting |
| Character restrictions | Different platforms allow different characters | Stick to lowercase alphanumerics, underscores, and hyphens |
| SDK constant naming | Flag names are often stored as constants whose naming follows language conventions | Define the flag name once as a constant; the constant name follows language conventions |
Language-specific constant patterns
The flag name itself should be consistent across languages. The constant or variable name that holds it follows language conventions:
// Go: exported constant in PascalCase
const ReleaseUnifiedCheckout = "release_unified_checkout_2025q4"
func isCheckoutEnabled(ctx context.Context) bool {
return flagClient.BoolVariation(ReleaseUnifiedCheckout, ctx, false)
}
// TypeScript: UPPER_SNAKE_CASE constant
const RELEASE_UNIFIED_CHECKOUT = "release_unified_checkout_2025q4";
function isCheckoutEnabled(user: User): boolean {
return flagClient.boolVariation(RELEASE_UNIFIED_CHECKOUT, user, false);
}
# Python: UPPER_SNAKE_CASE constant
RELEASE_UNIFIED_CHECKOUT = "release_unified_checkout_2025q4"
def is_checkout_enabled(user_context: dict) -> bool:
return flag_client.variation(RELEASE_UNIFIED_CHECKOUT, user_context, False)
// Kotlin: companion object constant
companion object {
const val RELEASE_UNIFIED_CHECKOUT = "release_unified_checkout_2025q4"
}
fun isCheckoutEnabled(context: LDContext): Boolean {
return flagClient.boolVariation(RELEASE_UNIFIED_CHECKOUT, context, false)
}
Key principle: The flag name string (release_unified_checkout_2025q4) is identical across all languages. Only the constant name that holds it varies to match language conventions. This ensures that searching for the flag name across the entire codebase returns every reference, regardless of language.
Centralizing flag name definitions
For teams working across multiple services, define flag names in a single shared location:
# flags.yaml -- Single source of truth for all flag names
flags:
- key: release_unified_checkout_2025q4
type: release
owner: "@jane.doe"
expires: 2026-03-31
description: "Controls rollout of unified checkout experience"
services:
- checkout-api
- web-frontend
- mobile-ios
- mobile-android
- key: experiment_search_ranking_boost
type: experiment
owner: "@john.smith"
expires: 2026-02-28
description: "Tests boosted ranking algorithm for search results"
services:
- search-service
- web-frontend
This approach provides a single inventory of all flags, their names, owners, and the services that reference them. It also makes it straightforward for automated tools to validate naming compliance and track flag usage across services.
How naming conventions affect automated detection
Automated flag detection tools parse your codebase to find flag references, track their lifecycle, and generate cleanup PRs. The quality of your naming convention directly affects how well these tools work.
What detection tools look for
Tools like FlagShark use tree-sitter AST parsing to identify flag SDK method calls and extract the flag key from the arguments. The detection pipeline works like this:
- Parse the source file into an AST
- Find method calls matching known SDK patterns (e.g.,
client.BoolVariation(...)) - Extract the flag key from the argument at the configured position
- Record the flag name, file location, and whether it was added or removed in the PR diff
The flag name is the primary identifier. It is how the tool connects a flag reference in checkout_service.go to the same flag reference in CheckoutPage.tsx. If the names do not match exactly, the tool cannot track the flag across services.
How good naming helps automation
| Naming Practice | Automation Benefit |
|---|---|
| Consistent type prefixes | Tools can automatically categorize flags and apply type-specific lifecycle rules |
| Date encoding | Tools can calculate flag age from the name alone, without querying creation history |
| Standardized separators | Tools can reliably parse names into components for filtering and reporting |
| No version suffixes | Tools do not confuse checkout_v2 and checkout_v3 as the same flag |
| Cross-language consistency | Tools can match flags across services by searching for the exact same string |
How bad naming breaks automation
Consider what happens when an automated tool encounters these flags:
enableNewCheckout # No type prefix, no separator convention
new-checkout-enabled # Different separator, different word order
ENABLE_CHECKOUT_V2 # Different case, version suffix
checkout.new.enable # Period separators, different structure
Are these the same flag? Different flags? Without a naming convention, there is no way to know programmatically. A human might recognize them as related, but automated tools cannot make that inference. Each inconsistency forces manual intervention, which defeats the purpose of automation.
Consistent naming is a prerequisite for automated lifecycle management. If you plan to use any tool that tracks flags across your codebase -- whether it is FlagShark, Piranha, a custom script, or even grep -- naming conventions are foundational.
The naming convention template
Below is a ready-to-adopt naming convention document. Copy it, customize the values for your team, and distribute it as part of your engineering standards.
FEATURE FLAG NAMING CONVENTION
Version: 1.0 Effective Date: [Date] Owner: [Engineering Manager / Tech Lead] Applies To: All repositories and services owned by [Team / Organization]
Format
{type}_{feature_description}_{context}_{date}
- Separators: Underscores (
_) between components, hyphens (-) within components - Case: All lowercase
- Maximum length: 80 characters
- Required components: type, feature_description
- Recommended components: date
- Optional components: context
Type Prefixes
| Prefix | Use When | Expected Lifespan | Example |
|---|---|---|---|
release_ | Rolling out a new feature to users | 2-12 weeks | release_unified_checkout_2025q4 |
experiment_ | Running an A/B test or experiment | 2-6 weeks | experiment_pricing_page_cta_variant |
ops_ | Operational kill switch or circuit breaker | Permanent | ops_disable_payment_processing |
perm_ | Access control for user segments or tiers | 3-6 months | perm_beta_users_analytics_dashboard |
migration_ | Controlling traffic during a system migration | Duration of migration | migration_postgres_to_dynamodb_2025q4 |
hotfix_ | Temporary fix with a planned permanent solution | 1-4 weeks | hotfix_api_timeout_fallback_202601 |
Feature Description
- Use 2-5 words that describe what the flag controls
- Separate words with hyphens within the component
- Be specific enough that another engineer can understand the purpose without additional context
- Avoid generic terms:
new,old,test,temp,v2
Good: unified-checkout, search-ranking-boost, billing-annual-plans
Bad: new-feature, test-thing, v2, fix
Context (Optional)
Add context when the feature description alone is ambiguous:
| Context Type | When to Use | Examples |
|---|---|---|
| Platform | Flag behavior differs by platform | _mobile, _web, _ios, _api |
| Region | Flag is region-specific | _eu, _us, _apac |
| Service | Flag is used in a specific service only | _checkout-api, _search-svc |
Date
- Recommended format:
YYYYqN(quarter) orYYYYMM(month) - Encodes when the flag was created, not when it should expire
- Makes stale flags visually obvious when scanning code
Prohibited Patterns
| Pattern | Example | Why Prohibited |
|---|---|---|
| Personal names | johns_feature | People leave; names become meaningless |
temp_ prefix | temp_fix_auth | "Temporary" is not a flag type |
test_ prefix | test_new_flow | Test flags should not exist in production code |
| Version suffixes | checkout_v2 | Versions create infinite chains |
| Boolean verbs | enable_dark_mode | Describes evaluation result, not purpose |
| Abbreviations without context | rel_uc_q4 | Unreadable to anyone who did not create it |
| Spaces or special characters | New Checkout! | Breaks most SDKs and tools |
Examples
Correct:
release_unified_checkout_2025q4
experiment_search-ranking-boost_web_202601
ops_circuit-breaker_payment-processing
perm_enterprise-analytics_api
migration_auth-service_oauth2_2025q4
hotfix_cart-total-rounding_202601
Incorrect:
new_checkout # No type prefix, no date
enableNewCheckout # CamelCase, boolean verb, no type
temp_fix_123 # temp prefix, no description
release-checkout-v2-FINAL # Version suffix, mixed case
johns_experiment_thing # Personal name, vague description
Enforcing naming conventions in practice
A naming convention that exists only in a document will be followed by 30% of your team on a good day. Enforcement requires integrating the convention into the development workflow at the point where flags are created.
Code review enforcement
The simplest enforcement mechanism is a code review checklist item. When a PR introduces a new flag, reviewers verify:
- The flag name follows the
{type}_{feature}_{context}_{date}format - The type prefix matches the flag's actual purpose
- The feature description is specific and readable
- No prohibited patterns are used
This works for small teams but does not scale past 10-15 engineers. Manual review is inconsistent and creates friction between reviewers and authors.
Automated linting
For teams with CI/CD pipelines, a naming convention can be enforced by a lint rule that validates new flag names against a regex:
^(release|experiment|ops|perm|migration|hotfix)_[a-z][a-z0-9-]*(_[a-z][a-z0-9-]*)*(_\d{4}(q[1-4]|\d{2}))?$
This regex validates:
- Starts with an allowed type prefix
- Feature description uses lowercase alphanumerics and hyphens
- Optional context segments follow the same rules
- Optional date in quarter or month format
A CI check that runs this regex against newly introduced flag names catches violations before they merge. The error message should include the naming convention document link so engineers can self-correct.
Platform-level enforcement
Some flag management platforms support naming validation rules. LaunchDarkly allows tag-based policies; Unleash supports custom validation via plugins. If your platform supports it, enforce naming at the platform level so that non-compliant flags cannot be created in the first place.
Retroactive cleanup
For existing flags that predate the convention, apply the convention gradually:
- Audit current flags. Categorize them by the convention's type prefixes.
- Rename during cleanup. When a flag comes up for removal or modification, rename it to comply with the convention before any other work.
- Do not rename all flags at once. A mass rename creates a disruptive PR, confuses automated tracking tools, and risks breaking references. Rename incrementally as flags are touched.
Naming conventions across flag providers
Different flag providers have different constraints and conventions. Here is how the naming convention adapts to major providers:
| Provider | Character Limit | Allowed Characters | Recommended Separator | Example |
|---|---|---|---|---|
| LaunchDarkly | 256 | Alphanumeric, ., -, _ | _ or - | release_unified-checkout_2025q4 |
| Unleash | 255 | Alphanumeric, ., -, _, : | - or . | release.unified-checkout.2025q4 |
| Split.io | 250 | Alphanumeric, -, _, . | _ | release_unified_checkout_2025q4 |
| Flagsmith | 255 | Alphanumeric, -, _ | _ | release_unified_checkout_2025q4 |
| Custom / in-code | Varies | Varies | _ | release_unified_checkout_2025q4 |
The convention works with all major providers. The only adjustment needed is the separator character if your provider has specific preferences. The component structure -- type, feature, context, date -- remains the same.
Measuring naming convention adoption
After rolling out the convention, track adoption to ensure it takes hold:
| Metric | Target | Measurement |
|---|---|---|
| New flags compliant with convention | > 95% within 1 month | Automated lint check pass rate |
| Existing flags renamed to convention | > 50% within 1 quarter | Audit of flag inventory |
| Flags with type prefix | 100% of new flags | Platform or codebase query |
| Flags with date component | > 80% of new flags | Platform or codebase query |
| Average flag name length | 30-60 characters | Platform analytics |
| Time to identify stale flags in audit | < 5 seconds per flag | Team exercise timing |
The last metric is the most telling. Run a quick exercise with your team: show them a list of 20 flag names and ask them to identify which ones are stale. With good naming conventions (type prefixes and dates), engineers can identify stale flags in 2-3 seconds each. Without conventions, it takes 30-60 seconds per flag plus a trip to the management platform.
Getting started this week
You do not need buy-in from the entire organization to start improving flag names. Begin with your own team:
Day 1: Share this article and the naming convention template with your team. Discuss in standup (10 minutes).
Day 2-3: Agree on your type prefixes and date format. Customize the template for your team's specific needs (platforms, services, flag types).
Week 1: Apply the convention to all new flags. Add a code review checklist item for flag naming compliance.
Week 2-4: Set up automated linting if your CI/CD pipeline supports it. Begin renaming existing flags incrementally as they are touched in normal development work.
Month 2: Run a flag audit. With the naming convention in place, stale flags should be identifiable from their names alone. The flags with dates from two or more quarters ago and release_ or experiment_ prefixes are your cleanup candidates.
Tools like FlagShark can accelerate this process by automatically detecting flag names across your codebase, tracking whether they follow your convention, and generating cleanup PRs for stale flags that the naming convention makes easy to identify. But the convention itself is the foundation -- it works whether your cleanup process is manual, semi-automated, or fully automated.
A feature flag name is a contract with your future self and every engineer who will encounter that flag after you. A good name says: "I am a release flag for the unified checkout, created in Q4 2025, and I should be gone by now if you are reading this in Q2 2026." A bad name says nothing, and silence is what turns flags into permanent fixtures.
The convention takes 30 minutes to establish and 10 seconds to follow on every flag you create. The cleanup hours it saves over the lifetime of your codebase will number in the hundreds. Start this week.