[{"data":1,"prerenderedAt":454},["ShallowReactive",2],{"blog-post-\u002Fblog\u002Fsafe-renaming-rename-a-feature-flag-thats-already-in-production":3},{"id":4,"title":5,"author":6,"body":7,"date":442,"description":443,"draft":444,"extension":445,"image":446,"meta":447,"navigation":255,"path":448,"seo":449,"sitemap":450,"stem":452,"__hash__":453},"blog\u002Fblog\u002Fsafe-renaming-rename-a-feature-flag-thats-already-in-production.md","Safe renaming: how to rename a feature flag that's already in production","Alejandro Beiderman",{"type":8,"value":9,"toc":433},"minimark",[10,14,41,44,47,50,55,58,66,69,72,76,79,97,100,107,113,117,120,127,138,148,341,344,348,360,367,378,381,384,387,391,400,403,407,410,413,417,420,429],[11,12,13],"p",{},"Every project has a feature flag or a config value with an outdated name:",[15,16,17,25,31],"ul",{},[18,19,20,24],"li",{},[21,22,23],"code",{},"new-checkout-options"," is three years old.",[18,26,27,30],{},[21,28,29],{},"temp-hide-pricing"," is now a permanent kill switch.",[18,32,33,36,37,40],{},[21,34,35],{},"cart-experiment-2"," is live, and nobody remembers what ",[21,38,39],{},"cart-experiment-1"," was.",[11,42,43],{},"The names made sense when they were created. Perhaps it was the original plan for them to be short-lived, but then things changed and now it is useful to preserve them as long-lived kill switches or config values. Sometimes, it was a matter of finding a new and better name for the feature after it shipped, but the old flag\u002Fconfig name stayed behind.",[11,45,46],{},"Nobody renames them because in most feature flag services the key is the one thing you are not allowed to change. Sometimes, you get a name field that you can edit, but the key that is used in the code is still the old outdated name. The market settled on immutable keys, and everyone learned to live with the keys they picked on day one, or copy the flag with a new key and hope they don't get out of sync while the new key is being updated in the code.",[11,48,49],{},"That is not a good trade-off. The key is part of the code. It's documentation, it should have a useful meaning. When it is expensive to fix it, it stays behind and rots.",[51,52,54],"h2",{"id":53},"why-renaming-a-flag-is-hard","Why renaming a flag is hard",[11,56,57],{},"A flag or config key is a contract between the flag service and your applications. On the flag service side, a rename is one row in a database table. In your code, it is every place that reads the key, in every application, at every version that is still deployed and the new versions that will deploy next.",[11,59,60,61,65],{},"For a single web service, you ",[62,63,64],"em",{},"could"," update the key right before deploying, and accept that for a brief period of time the application will serve the in-code default value. But that is far from ideal, and it doesn't work for client applications since, in general, you don't control when users upgrade to the new version.",[11,67,68],{},"There are many situations where simply updating the key in place wouldn't work. A mobile app has users on builds from six months ago, and those builds will keep asking for the old key for as long as anyone runs them. A backend system, split across multiple services, has code that reads the flag in three repositories owned by two different teams. A one-off script somebody wrote to help with production support reads it too, and nobody remembers the script.",[11,70,71],{},"There is no single moment when you can update the flag to the new name without negatively affecting what's already running in production.",[51,73,75],{"id":74},"the-workaround-everyone-uses","The workaround everyone uses",[11,77,78],{},"Since flag services do not have a rename option, most teams do it by hand:",[80,81,82,85,88,91,94],"ol",{},[18,83,84],{},"Create a second flag with the better name.",[18,86,87],{},"Copy the targeting rules from the old one, in every environment.",[18,89,90],{},"Keep the two in sync while the code is migrated, and do not forget to update one of them when someone changes a rollout on a Friday.",[18,92,93],{},"Migrate the code, repository by repository.",[18,95,96],{},"Delete the old flag when you think it is safe.",[11,98,99],{},"Step 3 has the biggest window of opportunity for things to go wrong. Two duplicated flags for the same purpose drift, and the moment they disagree you have a rollout that behaves differently for users depending on which build they are on.",[11,101,102,103,106],{},"Step 5, \"when you think it is safe\", is a best guess, and perhaps ",[62,104,105],{},"we should leave it a little longer just to be safe",", which in turn makes the window of opportunity grow larger for step 3 to go wrong.",[11,108,109,110,112],{},"Most teams look at that list, decide the old name is not that bad, and move on. That is how ",[21,111,23],{}," turned three years old last May.",[51,114,116],{"id":115},"the-solution-one-config-two-keys","The solution: One config, two keys",[11,118,119],{},"ConfigDirector treats a rename as what it should be: a period of time when the config responds to two different keys.",[11,121,122,123,126],{},"When you rename a config from the dashboard, one of two things will happen. If the config is still ",[21,124,125],{},"New"," and has never been evaluated, the key is simply updated in place. There is nothing in production that would break.",[11,128,129,130,133,134,137],{},"If the config is in use, the new key becomes the config's ",[62,131,132],{},"primary"," key and the old key is kept as a ",[62,135,136],{},"secondary"," key on the same config. It's not a copy. It's the same config, with the same targeting rules, the same values in every environment, the same audit history. It now answers to both keys.",[11,139,140,141,143,144,147],{},"An application that asks for ",[21,142,23],{}," gets the same evaluation as one that asks for ",[21,145,146],{},"express-checkout-options",". You update the code at whatever pace your release process allows, without worrying about the old key going away:",[149,150,155],"pre",{"className":151,"code":152,"language":153,"meta":154,"style":154},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const defaultCheckoutOptions = {\n  expressProviders: [\"apple-pay\", \"google-pay\"],\n  showGuestCheckout: true,\n  maxSavedCards: 3,\n};\n\n\u002F\u002F Old builds keep working\nconst checkoutOptions = client.getValue(\"new-checkout-options\", defaultCheckoutOptions);\n\n\u002F\u002F New builds use the new name, same config, same rules\nconst checkoutOptions = client.getValue(\"express-checkout-options\", defaultCheckoutOptions);\n","ts","",[21,156,157,177,216,230,244,250,257,264,301,306,312],{"__ignoreMap":154},[158,159,162,166,170,174],"span",{"class":160,"line":161},"line",1,[158,163,165],{"class":164},"spNyl","const",[158,167,169],{"class":168},"sTEyZ"," defaultCheckoutOptions ",[158,171,173],{"class":172},"sMK4o","=",[158,175,176],{"class":172}," {\n",[158,178,180,184,187,190,193,197,199,202,205,208,210,213],{"class":160,"line":179},2,[158,181,183],{"class":182},"swJcz","  expressProviders",[158,185,186],{"class":172},":",[158,188,189],{"class":168}," [",[158,191,192],{"class":172},"\"",[158,194,196],{"class":195},"sfazB","apple-pay",[158,198,192],{"class":172},[158,200,201],{"class":172},",",[158,203,204],{"class":172}," \"",[158,206,207],{"class":195},"google-pay",[158,209,192],{"class":172},[158,211,212],{"class":168},"]",[158,214,215],{"class":172},",\n",[158,217,219,222,224,228],{"class":160,"line":218},3,[158,220,221],{"class":182},"  showGuestCheckout",[158,223,186],{"class":172},[158,225,227],{"class":226},"sfNiH"," true",[158,229,215],{"class":172},[158,231,233,236,238,242],{"class":160,"line":232},4,[158,234,235],{"class":182},"  maxSavedCards",[158,237,186],{"class":172},[158,239,241],{"class":240},"sbssI"," 3",[158,243,215],{"class":172},[158,245,247],{"class":160,"line":246},5,[158,248,249],{"class":172},"};\n",[158,251,253],{"class":160,"line":252},6,[158,254,256],{"emptyLinePlaceholder":255},true,"\n",[158,258,260],{"class":160,"line":259},7,[158,261,263],{"class":262},"sHwdD","\u002F\u002F Old builds keep working\n",[158,265,267,269,272,274,277,280,284,287,289,291,293,295,298],{"class":160,"line":266},8,[158,268,165],{"class":164},[158,270,271],{"class":168}," checkoutOptions ",[158,273,173],{"class":172},[158,275,276],{"class":168}," client",[158,278,279],{"class":172},".",[158,281,283],{"class":282},"s2Zo4","getValue",[158,285,286],{"class":168},"(",[158,288,192],{"class":172},[158,290,23],{"class":195},[158,292,192],{"class":172},[158,294,201],{"class":172},[158,296,297],{"class":168}," defaultCheckoutOptions)",[158,299,300],{"class":172},";\n",[158,302,304],{"class":160,"line":303},9,[158,305,256],{"emptyLinePlaceholder":255},[158,307,309],{"class":160,"line":308},10,[158,310,311],{"class":262},"\u002F\u002F New builds use the new name, same config, same rules\n",[158,313,315,317,319,321,323,325,327,329,331,333,335,337,339],{"class":160,"line":314},11,[158,316,165],{"class":164},[158,318,271],{"class":168},[158,320,173],{"class":172},[158,322,276],{"class":168},[158,324,279],{"class":172},[158,326,283],{"class":282},[158,328,286],{"class":168},[158,330,192],{"class":172},[158,332,146],{"class":195},[158,334,192],{"class":172},[158,336,201],{"class":172},[158,338,297],{"class":168},[158,340,300],{"class":172},[11,342,343],{},"There is nothing to keep in sync, because there is still only one config.",[51,345,347],{"id":346},"knowing-when-the-rename-is-finished","Knowing when the rename is finished",[11,349,350,351,359],{},"The hard part of a rename is not attaching multiple keys to the config. It is knowing when the old key is dead and can be cleaned up. ConfigDirector already knows, because the SDKs report every evaluation with the key that was used to make it, and that telemetry is what drives the ",[352,353,358],"a",{"href":354,"rel":355,"target":357},"https:\u002F\u002Fdocs.configdirector.com\u002Fmonitoring\u002Flifecycle",[356],"noopener","_blank","lifecycle"," and cleanup features.",[11,361,362,363,186],{},"A recurring job looks at every config with a rename in progress and removes the old key when ",[364,365,366],"strong",{},"all three of these are true",[15,368,369,372,375],{},[18,370,371],{},"The old key has existed for at least 7 days.",[18,373,374],{},"The old key has not been evaluated by any SDK, in any environment, for 7 days.",[18,376,377],{},"The new key has been evaluated in that same window.",[11,379,380],{},"The third condition is crucial for safety. If nothing is asking for the new key yet, we can't know that the migration in the code took place. The old key stays attached to the config until the new key is read in production.",[11,382,383],{},"If a forgotten build keeps asking for the old key, the old key keeps working. A mobile release that takes four months to age out delays the cleanup by four months, and that is the point. The rename is done when production usage says it is done, not when the calendar does.",[11,385,386],{},"While a rename is in progress, the config's Settings tab shows a \"Rename in progress\" badge with the old key pointing at the new one, and the audit log records the rename with both keys. A config can have one rename in progress at a time. The current one has to complete before you can rename it again, which keeps the mapping simple and manageable: one config, one current name, at most one previous name.",[51,388,390],{"id":389},"the-safety-net-for-everything-else","The safety net for everything else",[11,392,393,394,399],{},"Suppose you get it wrong anyway. Some old forgotten application all of a sudden shows up long after the rename was done and requests the old key. Or an application ships with a typo in the new key. In ConfigDirector that is a ",[352,395,398],{"href":396,"rel":397,"target":357},"https:\u002F\u002Fdocs.configdirector.com\u002Fmonitoring\u002Falerts#config-not-found",[356],"Config not found alert",", raised at High priority with no grace period, naming the key that was requested and the SDKs that requested it. You'll find out from the service, not from a user.",[11,401,402],{},"That alert, and other integrity alerts, exist because the whole design rests on the idea that the service sees every evaluation, so it should be the one to notice when code and configuration disagree. Safe renaming is the same idea applied to a change that most other services disallow, shifting the burden onto the engineers.",[51,404,406],{"id":405},"whats-coming-next","What's coming next",[11,408,409],{},"Renaming is a dashboard action today. The Admin API does not yet expose it, so a rename does not happen from a pipeline or a coding assistant. The cleanup waits at least 7 days even when the code migrated in an hour, because 7 days is the window we use to decide a key is unused everywhere.",[11,411,412],{},"The 7-day window is currently fixed, but will become an overridable default (with some safeguards) in the near future when we ship renaming to the Admin API and MCP server as well.",[51,414,416],{"id":415},"try-it","Try it",[11,418,419],{},"Create a flag, read it from any SDK, and rename it in the Settings tab. The dashboard tells you before you save whether the rename will happen in place or the old key will be kept during the process. Then change the code to the new key and keep reading. Both names evaluate identically, and a week after the old name stops being used, it is gone.",[11,421,422,423,428],{},"The ",[352,424,427],{"href":425,"rel":426,"target":357},"https:\u002F\u002Fdocs.configdirector.com\u002Fgetting-started\u002Fconfigs#key",[356],"docs on config keys"," cover the rules. The Free plan has no credit card requirement and includes all of it.",[430,431,432],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":154,"searchDepth":179,"depth":179,"links":434},[435,436,437,438,439,440,441],{"id":53,"depth":179,"text":54},{"id":74,"depth":179,"text":75},{"id":115,"depth":179,"text":116},{"id":346,"depth":179,"text":347},{"id":389,"depth":179,"text":390},{"id":405,"depth":179,"text":406},{"id":415,"depth":179,"text":416},"2026-09-26","Most feature flag services treat the key as permanent. Here is why that is not a good trade-off, and how ConfigDirector lets you rename a flag while old builds are still asking for the old key.",false,"md","\u002Fsafe-renaming-rename-a-feature-flag-thats-already-in-production-cover.webp",{},"\u002Fblog\u002Fsafe-renaming-rename-a-feature-flag-thats-already-in-production",{"title":5,"description":443},{"loc":448,"lastmod":451},"2026-09-28T00:30:23.000Z","blog\u002Fsafe-renaming-rename-a-feature-flag-thats-already-in-production","FjWSa4FW9b-XALt6Cpd0ZpWRlgYTv2G_Wd-4lnf0eNs",1790555515442]