<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: ConfigDirector</title>
    <description>The latest articles on DEV Community by ConfigDirector (configdirector).</description>
    <link>https://dev.to/configdirector</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F14812%2Fee165f35-c545-4780-827e-536d4cd099e2.png</url>
      <title>DEV Community: ConfigDirector</title>
      <link>https://dev.to/configdirector</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/configdirector"/>
    <language>en</language>
    <item>
      <title>Remote config for Nuxt in five minutes</title>
      <dc:creator>Alejandro Beiderman</dc:creator>
      <pubDate>Wed, 07 Oct 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/configdirector/remote-config-for-nuxt-in-five-minutes-3e4n</link>
      <guid>https://dev.to/configdirector/remote-config-for-nuxt-in-five-minutes-3e4n</guid>
      <description>&lt;p&gt;Nuxt renders a page twice, once on the server via SSR, and then again in the browser. That is good for users but can be tricky for feature flags. If the server renders the page with the flag off and the browser decides it is on, you get a flash of the wrong content and a hydration warning in the console. Most flag SDKs leave it up to you to wire the server and the browser yourself and to make sure they are synced up.&lt;/p&gt;

&lt;p&gt;The ConfigDirector Nuxt module does it for you. In this post we will add a flag to a Nuxt app, have it render on the server, flip it live in the browser, read it from a server route, and update it to target a group of users. It takes about five minutes with a running Nuxt app.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Create the flag
&lt;/h2&gt;

&lt;p&gt;Sign in to the &lt;a href="https://app.configdirector.com/" rel="noopener noreferrer"&gt;dashboard&lt;/a&gt; and create a config. For this post the key is &lt;code&gt;new-pricing-banner&lt;/code&gt;, the type is Boolean, and the SDK availability is both client and server. The availability matters for Nuxt: the browser and the server read the same flag, so it has to be available to both.&lt;/p&gt;

&lt;p&gt;The key is what your code reads, so pick the name you want to see in the code. If you change your mind later, &lt;a href="https://www.configdirector.com/blog/safe-renaming-rename-a-feature-flag-thats-already-in-production" rel="noopener noreferrer"&gt;renaming it is safe&lt;/a&gt;, even in production.&lt;/p&gt;

&lt;p&gt;Leave the value &lt;code&gt;false&lt;/code&gt; in every environment for now. You will turn it on from the dashboard once the app is reading it.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Install the module
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--save&lt;/span&gt; @configdirector/nuxt-sdk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register the module and give it the two SDK keys from the SDK Keys page of your project. Each environment has its own pair. The client key is public and ends up in the browser bundle. The server key is a secret and belongs in an environment variable, not in the code repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineNuxtConfig&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@configdirector/nuxt-sdk&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;runtimeConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;public&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;configdirector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;clientSdkKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;YOUR-CLIENT-SDK-KEY&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;configdirector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;serverSdkKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NUXT_CONFIGDIRECTOR_SERVER_SDK_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR-SERVER-SDK-KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nuxt's runtime config fills &lt;code&gt;serverSdkKey&lt;/code&gt; from that variable at startup, so the same build works in every environment with a different key. The client key can be overridden the same way with &lt;code&gt;NUXT_PUBLIC_CONFIGDIRECTOR_CLIENT_SDK_KEY&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That is the whole setup. There is no plugin to write and no provider component to wrap the app in.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Read it in a page
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;useConfigDirectorValue&lt;/code&gt; composable takes the key and the in-code default value, which is the value your code uses if the flag cannot be evaluated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;showNewBanner&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConfigDirectorValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-pricing-banner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;section&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PricingBanner&lt;/span&gt; &lt;span class="na"&gt;v-if=&lt;/span&gt;&lt;span class="s"&gt;"showNewBanner"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;PricingTable&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/section&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the dev server and load the page. The server rendered it with the real value of the flag, and the browser picked up from there. There is no flash and no hydration mismatch, because both sides evaluated the same flag against the same context before anything was sent.&lt;/p&gt;

&lt;p&gt;Two things make that work. On the server, the module keeps one long-lived client in Nitro that connects to ConfigDirector when the server starts (which requires the server SDK key). Requests that arrive before the first payload are held, for up to three seconds by default, so even the first request after a deploy renders real values rather than defaults. In the browser, the client connects on app creation, before any page component mounts, and the composable's &lt;code&gt;value&lt;/code&gt; is a &lt;code&gt;ShallowRef&lt;/code&gt; that updates when the flag changes.&lt;/p&gt;

&lt;p&gt;Back in the dashboard's Getting Started guide, the Connect SDK page of your project is listening for the first evaluation. It flips to "Your app is connected" within seconds of the page load, which is how you know everything is configured correctly before you go any further.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Flip it live
&lt;/h2&gt;

&lt;p&gt;Set &lt;code&gt;new-pricing-banner&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; in the test/dev environment and save. Look at the browser tab. The banner appears without a reload.&lt;/p&gt;

&lt;p&gt;The browser client holds a streaming connection, so a change in the dashboard reaches every open tab within moments of saving. The server SDK holds its own streaming connection, so the next server-rendered request shows the banner too. Nothing in the app had to poll or refresh.&lt;/p&gt;

&lt;p&gt;If you prefer, either the client or the server SDK connection can poll instead of stream. The server defaults to every five minutes and the browser to every minute. Streaming is the default on both and the right choice unless your network setup rules it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Read it in a server route
&lt;/h2&gt;

&lt;p&gt;Server routes and middleware use the same Nitro client through the server-side &lt;code&gt;useConfigDirectorClient&lt;/code&gt; composable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConfigDirectorClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewBanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-pricing-banner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;showNewBanner&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The server SDK client evaluates targeting rules locally. Evaluating a flag in a route does not make a network request. It is a lookup against the payload the SDK already holds in memory, so the cost of evaluating flags in hot paths is an in-memory hash map lookup.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Target it to some users
&lt;/h2&gt;

&lt;p&gt;ConfigDirector evaluates the flag against a user context, which carries an ID, an optional name, and whatever traits you choose to send.&lt;/p&gt;

&lt;p&gt;In the browser, give the client the signed-in user after login:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
const &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;updateContext&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt; = useConfigDirectorContext();

async function onSignedIn(user: &lt;span class="si"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="si"&gt;}&lt;/span&gt;) &lt;span class="si"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;updateContext&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a server route, pass the user context as the third argument to &lt;code&gt;getValue&lt;/code&gt;, built from the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nf"&gt;defineEventHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;requireUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useConfigDirectorClient&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;showNewBanner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-pricing-banner&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;traits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;plan&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plan&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;showNewBanner&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add a targeting rule in the dashboard: &lt;code&gt;new-pricing-banner&lt;/code&gt; is &lt;code&gt;true&lt;/code&gt; when &lt;code&gt;plan&lt;/code&gt; equals &lt;code&gt;free&lt;/code&gt;, and &lt;code&gt;false&lt;/code&gt; otherwise. Or roll it out to 20 percent of users by ID and raise the percentage over the week. The rule saves, and both the server and the browser start applying it on their next evaluation. The browser re-evaluates on its own as soon as the context changes, so a user who signs in sees their own version of the page without a reload.&lt;/p&gt;

&lt;p&gt;Before saving a rule, the tester on the targeting rules tab shows which rule a given context matches and why, so you can check the free-plan rule against a real user ID before anyone else does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond a boolean
&lt;/h2&gt;

&lt;p&gt;Nothing above is specific to feature flags. The same composable reads any config type: a number for a page size, a string for a headline, a JSON document for a whole section of the page. Those configs can carry constraints of your choosing, so a page size can be an integer between 10 and 100 and the dashboard refuses anything else. The &lt;a href="https://www.configdirector.com/blog/why-your-feature-flag-service-should-validate-values" rel="noopener noreferrer"&gt;earlier post on validation&lt;/a&gt; explains why that belongs in the service and not in your code.&lt;/p&gt;

&lt;p&gt;When you test the components that read these configs, the SDK's testing entry point gives you the real client connected to an in-memory server your test controls, so a test can set &lt;code&gt;new-pricing-banner&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt;, mount the page, and assert the banner rendered, with no network and no mocks of the composable. The &lt;a href="https://docs.configdirector.com/sdks/meta/nuxt#test-your-components" rel="noopener noreferrer"&gt;docs cover the setup&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.configdirector.com/sdks/meta/nuxt" rel="noopener noreferrer"&gt;Nuxt SDK docs&lt;/a&gt; have every option: log levels, polling intervals, the startup hold, and client and server hooks. The module supports Nuxt 3.7 and later. The Free option needs no credit card and includes the SDK, streaming, targeting rules, and the alerts that tell you when code and configuration disagree.&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>devops</category>
      <category>nuxt</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Safe renaming: how to rename a feature flag that's already in production</title>
      <dc:creator>Alejandro Beiderman</dc:creator>
      <pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate>
      <link>https://dev.to/configdirector/safe-renaming-how-to-rename-a-feature-flag-thats-already-in-production-4moh</link>
      <guid>https://dev.to/configdirector/safe-renaming-how-to-rename-a-feature-flag-thats-already-in-production-4moh</guid>
      <description>&lt;p&gt;Every project has a feature flag or a config value with an outdated name:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;new-checkout-options&lt;/code&gt; is three years old.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;temp-hide-pricing&lt;/code&gt; is now a permanent kill switch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cart-experiment-2&lt;/code&gt; is live, and nobody remembers what &lt;code&gt;cart-experiment-1&lt;/code&gt; was.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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/config name stayed behind.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why renaming a flag is hard
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;For a single web service, you &lt;em&gt;could&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;There is no single moment when you can update the flag to the new name without negatively affecting what's already running in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workaround everyone uses
&lt;/h2&gt;

&lt;p&gt;Since flag services do not have a rename option, most teams do it by hand:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a second flag with the better name.&lt;/li&gt;
&lt;li&gt;Copy the targeting rules from the old one, in every environment.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;Migrate the code, repository by repository.&lt;/li&gt;
&lt;li&gt;Delete the old flag when you think it is safe.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Step 5, "when you think it is safe", is a best guess, and perhaps &lt;em&gt;we should leave it a little longer just to be safe&lt;/em&gt;, which in turn makes the window of opportunity grow larger for step 3 to go wrong.&lt;/p&gt;

&lt;p&gt;Most teams look at that list, decide the old name is not that bad, and move on. That is how &lt;code&gt;new-checkout-options&lt;/code&gt; turned three years old last May.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution: One config, two keys
&lt;/h2&gt;

&lt;p&gt;ConfigDirector treats a rename as what it should be: a period of time when the config responds to two different keys.&lt;/p&gt;

&lt;p&gt;When you rename a config from the dashboard, one of two things will happen. If the config is still &lt;code&gt;New&lt;/code&gt; and has never been evaluated, the key is simply updated in place. There is nothing in production that would break.&lt;/p&gt;

&lt;p&gt;If the config is in use, the new key becomes the config's &lt;em&gt;primary&lt;/em&gt; key and the old key is kept as a &lt;em&gt;secondary&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;An application that asks for &lt;code&gt;new-checkout-options&lt;/code&gt; gets the same evaluation as one that asks for &lt;code&gt;express-checkout-options&lt;/code&gt;. You update the code at whatever pace your release process allows, without worrying about the old key going away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;defaultCheckoutOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;expressProviders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;apple-pay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;google-pay&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;showGuestCheckout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;maxSavedCards&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// Old builds keep working&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkoutOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;new-checkout-options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultCheckoutOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// New builds use the new name, same config, same rules&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;checkoutOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express-checkout-options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;defaultCheckoutOptions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is nothing to keep in sync, because there is still only one config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Knowing when the rename is finished
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://docs.configdirector.com/monitoring/lifecycle" rel="noopener noreferrer"&gt;lifecycle&lt;/a&gt; and cleanup features.&lt;/p&gt;

&lt;p&gt;A recurring job looks at every config with a rename in progress and removes the old key when &lt;strong&gt;all three of these are true&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The old key has existed for at least 7 days.&lt;/li&gt;
&lt;li&gt;The old key has not been evaluated by any SDK, in any environment, for 7 days.&lt;/li&gt;
&lt;li&gt;The new key has been evaluated in that same window.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  The safety net for everything else
&lt;/h2&gt;

&lt;p&gt;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 &lt;a href="https://docs.configdirector.com/monitoring/alerts#config-not-found" rel="noopener noreferrer"&gt;Config not found alert&lt;/a&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.configdirector.com/getting-started/configs#key" rel="noopener noreferrer"&gt;docs on config keys&lt;/a&gt; cover the rules. The Free plan has no credit card requirement and includes all of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.configdirector.com/blog/safe-renaming-rename-a-feature-flag-thats-already-in-production?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog&amp;amp;utm_content=safe-renaming" rel="noopener noreferrer"&gt;ConfigDirector blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>backend</category>
      <category>programming</category>
      <category>softwaredevelopment</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Why your feature flag service should validate values</title>
      <dc:creator>Alejandro Beiderman</dc:creator>
      <pubDate>Sat, 19 Sep 2026 17:22:17 +0000</pubDate>
      <link>https://dev.to/configdirector/why-your-feature-flag-service-should-validate-values-2hp5</link>
      <guid>https://dev.to/configdirector/why-your-feature-flag-service-should-validate-values-2hp5</guid>
      <description>&lt;p&gt;Every feature flag and remote config service offers the same value proposition: change how production behaves without deploying a new build. That is the purpose of the tool, but it is also the risk. A deploy goes through a CI pipeline running test suites, static analysis, and functional tests. A remote config change goes through a text box on a dashboard.&lt;/p&gt;

&lt;p&gt;Outages caused by a configuration update usually come down to small values that were slightly wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A timeout entered in seconds when the code expects milliseconds&lt;/li&gt;
&lt;li&gt;A URL pasted without the scheme&lt;/li&gt;
&lt;li&gt;A JSON document saved with a typo on one field, or a field missing&lt;/li&gt;
&lt;li&gt;A rollout percentage typed as &lt;code&gt;10&lt;/code&gt; into a field that expected &lt;code&gt;0.1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these mistakes is obvious in hindsight, but invisible at the moment someone clicks Save.&lt;/p&gt;

&lt;p&gt;The service hosting the value is the only thing standing between that click and your users. It should know what a valid value looks like, and prevent those mistakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  A value is not a string
&lt;/h2&gt;

&lt;p&gt;Most services store a configuration value as one of a few loose types: boolean, string, number, JSON. That is enough to render an appropriate input field, but it is not enough to know the value makes sense in the context it will be used.&lt;/p&gt;

&lt;p&gt;Your code and your application have specific expectations for each value. They expect the timeout to be a positive integer under thirty seconds. They expect an API endpoint to be HTTPS. They expect the theme to be one of &lt;code&gt;light&lt;/code&gt;, &lt;code&gt;dark&lt;/code&gt;, or &lt;code&gt;system&lt;/code&gt;. They expect the JSON payload to have a &lt;code&gt;version&lt;/code&gt; field, and an array of at most twenty items. None of that knowledge lives in the feature flag service, so the service cannot validate any of it.&lt;/p&gt;

&lt;p&gt;In ConfigDirector, every config has a fine-grained type, and each type carries the constraints your application actually depends on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Integer and Float&lt;/strong&gt; with minimum and maximum bounds. A &lt;code&gt;search-page-size&lt;/code&gt; config bounded to 10 through 100 cannot be saved as &lt;code&gt;0&lt;/code&gt; or as &lt;code&gt;10000&lt;/code&gt;. A &lt;code&gt;free-shipping-threshold&lt;/code&gt; bounded to 0 through 500 cannot be saved as a negative number or as ten times what you meant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timespan&lt;/strong&gt; for bounded durations, so the unit is part of the input field rather than a comment nobody reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;URL&lt;/strong&gt; with an HTTPS-only option. A URL without the scheme is no longer a possibility, nor is HTTP when you expect HTTPS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enum&lt;/strong&gt; with the allowed set of values. Not as variations of an experiment, but rather baked into the value type of a config used for any given purpose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON&lt;/strong&gt; with a JSON Schema, covered in detail below.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boolean&lt;/strong&gt; for feature flags and kill switches, which don't need complicated validation at save time, but do need monitoring and alerting for unexpected evaluation behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The constraints are enforced when a value is saved, in every environment, through all interfaces, before anything reaches an SDK. A bad value never leaves the dashboard, the Terraform file, or the code assistant chat window.&lt;/p&gt;

&lt;h2&gt;
  
  
  JSON is where it matters most
&lt;/h2&gt;

&lt;p&gt;JSON documents are the most powerful kind of remote config and also the most dangerous. A single document can drive a pricing table, a navigation menu, a set of rate limits, or an experiment's parameters. It is also the value most likely to be edited by hand or copied and pasted inadvertently from an unrelated JSON document, and most likely to fail at read time by the code consuming it.&lt;/p&gt;

&lt;p&gt;A JSON Schema turns the structure your code expects into something the service can validate and enforce. In ConfigDirector, a JSON config can carry a schema (draft-07), and from then on every value the config can return is validated against the schema before it is saved. All values that could be returned by the service are validated: the default targeting rule value, every conditional rule value, percentage rollout value, every experiment variation, in every environment.&lt;/p&gt;

&lt;p&gt;There is a second check. The schema itself is validated when you update it. Every existing value in every environment is checked against the new schema, and the schema change is rejected if any environment holds a value that would no longer pass. You cannot tighten a schema and leave staging holding a value that the schema would refuse. The values and the contract move together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation has to apply everywhere a value can change
&lt;/h2&gt;

&lt;p&gt;A check that only runs in the dashboard is a suggestion. Values change from more places than the dashboard, via the API, Terraform, a script someone wrote in an afternoon, and now AI coding assistants through an MCP server.&lt;/p&gt;

&lt;p&gt;That last one deserves a closer look. When an assistant creates a flag or updates a rollout on your behalf, it is working from a description of the task, not from your source code. It will produce a plausible value. Plausible is not the same as valid. If the service enforces the type and the schema on every write, the assistant gets the same rejection message a person would see in the dashboard, and it can correct itself. If the service does not, the plausible (but invalid) value is now live.&lt;/p&gt;

&lt;p&gt;In ConfigDirector, the same validations run for every path that can write a value. The dashboard, the Admin API, the Terraform provider, and the MCP server all go through it, and all of them get the same error messages.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SDK is the last line, not the first
&lt;/h2&gt;

&lt;p&gt;SDKs should be defensive, and ConfigDirector's are. If a value arrives that cannot be converted to the type your code asked for, the SDK returns the in-code default value you passed and reports why: the code requested a boolean but the config value is a string, or a JSON document that could not be coerced into the requested type. Your code keeps running.&lt;/p&gt;

&lt;p&gt;That fallback is the right behavior to keep the application from failing, but it should not be a &lt;em&gt;silent&lt;/em&gt; behavior. The in-code default value is usually the conservative choice and not the one you wanted in production. If a rollout falls back to "false" because the code asked for a boolean and the config is a string, the feature is not broken, it is just not happening.&lt;/p&gt;

&lt;p&gt;So the SDKs report the reason for every fallback in their telemetry, and ConfigDirector watches for patterns to alert on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type mismatches.&lt;/strong&gt; Code is requesting a config as a type it cannot be converted to, for example reading an Enum config as a boolean. You get an alert naming the config, the environment, and the SDK that is doing it, while it is still happening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-code default values that are not part of the targeting rules.&lt;/strong&gt; This is a classic problem. When you first add the flag or config to the code you give it an in-code fallback default that makes sense at the time. Later, things change, the value evolves, but the in-code default stays the same. If the evaluation ever failed, and the SDK had to fall back to the default, the in-code fallback value is outdated and likely no longer what you would want. ConfigDirector detects this and alerts you about in-code defaults that don't match any of the values configured in the targeting rules, meaning it is &lt;em&gt;very&lt;/em&gt; likely that the in-code default is outdated. You get notified as soon as the drift is detected, &lt;em&gt;before&lt;/em&gt; that outdated fallback value becomes a problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Validation on save means the mistake is caught by the person making it, at the moment they make it, with a message that says what was wrong. The alerts cover the other end, the code drifting away from the config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;Create a JSON config, attach a schema, and try to save a value that does not match it. Then change the schema so an existing value no longer passes and watch the service refuse the change. It is a powerful feature, and once you have it you will not want a config service without it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.configdirector.com/getting-started/json-schema-validation" rel="noopener noreferrer"&gt;docs on JSON Schema validation&lt;/a&gt; cover the limits and the details. The Free plan has no card and includes all of it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.configdirector.com/blog/why-your-feature-flag-service-should-validate-values?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=blog&amp;amp;utm_content=validate-values" rel="noopener noreferrer"&gt;ConfigDirector blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>devops</category>
      <category>json</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Managing feature flags from Claude Code and Cursor with MCP</title>
      <dc:creator>Alejandro Beiderman</dc:creator>
      <pubDate>Fri, 18 Sep 2026 01:47:26 +0000</pubDate>
      <link>https://dev.to/configdirector/managing-feature-flags-from-claude-code-and-cursor-with-mcp-2md0</link>
      <guid>https://dev.to/configdirector/managing-feature-flags-from-claude-code-and-cursor-with-mcp-2md0</guid>
      <description>&lt;p&gt;Most feature flag work happens in two places at once. You write the code that reads the flag in your editor, then switch to a dashboard to create the flag, enable it for dev/test, and later roll it out in production. With the ConfigDirector MCP server, the code assistant that is already in your editor can do the second half for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the MCP server is
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; is how coding assistants such as Claude Code, Cursor, and Copilot interact with outside tools. A server exposes a set of tools, the assistant reads the tool descriptions and calls them when a task needs them.&lt;/p&gt;

&lt;p&gt;ConfigDirector's MCP server is hosted, so there is nothing to install or run. It sits in front of the same Admin API that the dashboard and the Terraform provider use, and it authenticates with an ordinary API token. Every update it makes goes through the same validation as an update from the dashboard, it is recorded in the audit log with the source &lt;strong&gt;MCP&lt;/strong&gt;, and counts as one Admin API request per tool call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting Claude Code
&lt;/h2&gt;

&lt;p&gt;Create an API token in the dashboard with the permissions you want the assistant to have. For a first try, read permissions on projects, environments, and configs are enough to explore. To support a full workflow, add the create, update settings, and update targeting rules permissions on configs. Copy the token to your clipboard before dismissing the page (the token is only shown at creation time).&lt;/p&gt;

&lt;p&gt;Then add the MCP server to claude. The token is read from an environment variable, so it never lands in a file you might commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http configdirector https://mcp.configdirector.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONFIGDIRECTOR_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;/mcp&lt;/code&gt; inside Claude Code and the server shows as connected, with only the tools your token allows. Cursor, VS Code, Windsurf, Codex CLI, and Gemini CLI each have a two-line setup of their own. The &lt;a href="https://docs.configdirector.com/integrations/mcp" rel="noopener noreferrer"&gt;MCP docs page&lt;/a&gt; has all of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  A session
&lt;/h2&gt;

&lt;p&gt;Here is what an ordinary afternoon looks like once the server is connected. Each request is typed to the assistant in plain words, the tool calls happen underneath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a kill switch.&lt;/strong&gt; "Create a kill switch called &lt;code&gt;payments-v2&lt;/code&gt; in the checkout project, off in every environment." The assistant lists the projects to find checkout, creates the config with the kill switch role, and reports the key. The switch is off in every environment because that is the initial default targeting rule value it was created with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rolling out a flag.&lt;/strong&gt; "In production, roll &lt;code&gt;new-search&lt;/code&gt; out to 10 percent of users, and to everyone whose email ends in &lt;code&gt;@example.com&lt;/code&gt;." The assistant reads the flag's current rules for production, then replaces them with two rules: a conditional rule that matches the email domain, and a percentage rule that splits the rest. Because the MCP server provides the assistant with the exact targeting rules format and the operators each attribute supports, it does not have to guess.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Checking what is still in use.&lt;/strong&gt; "Which flags in the checkout project have not been evaluated in the last week?" The assistant reads the recent evaluation activity of each config, which comes from the SDKs' telemetry, and tells you which ones have not been requested by any SDK. Those are the candidates for cleanup, and the dashboard is where you archive them.&lt;/p&gt;

&lt;p&gt;Open the audit log afterwards and each change is there, attributed to the token's owner with the source &lt;strong&gt;MCP&lt;/strong&gt;, next to the changes made from the dashboard and from Terraform.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the server will not do
&lt;/h2&gt;

&lt;p&gt;The assistant only sees the tools its token has permission for. A read-only token produces a read-only assistant. There is no tool to archive a config, or to delete a project or an environment. Those destructive operations stay in the dashboard on purpose.&lt;/p&gt;

&lt;p&gt;Each organization can make up to 60 tool calls per minute. Past that, the assistant is told to wait for the next minute rather than being cut off, which keeps a retry loop from doing any harm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://docs.configdirector.com/integrations/mcp" rel="noopener noreferrer"&gt;docs page&lt;/a&gt; has the setup for every supported tool and the full list of what the assistant can do. If you already use ConfigDirector, an API token is all you need. If you do not, the Free plan includes the MCP server.&lt;/p&gt;

</description>
      <category>featureflags</category>
      <category>mcp</category>
      <category>ai</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
