Why your feature flag service should validate values

A feature flag service lets you change production without a deploy. It should also refuse the change that would break it.

Alejandro Beiderman · September 19, 2026

Why your feature flag service should validate values

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.

Outages caused by a configuration update usually come down to small values that were slightly wrong:

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

Each of these mistakes is obvious in hindsight, but invisible at the moment someone clicks Save.

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.

A value is not a string

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.

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 light, dark, or system. They expect the JSON payload to have a version 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.

In ConfigDirector, every config has a fine-grained type, and each type carries the constraints your application actually depends on:

  • Integer and Float with minimum and maximum bounds. A search-page-size config bounded to 10 through 100 cannot be saved as 0 or as 10000. A free-shipping-threshold bounded to 0 through 500 cannot be saved as a negative number or as ten times what you meant.
  • Timespan for bounded durations, so the unit is part of the input field rather than a comment nobody reads.
  • URL with an HTTPS-only option. A URL without the scheme is no longer a possibility, nor is HTTP when you expect HTTPS.
  • Enum 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.
  • JSON with a JSON Schema, covered in detail below.
  • Boolean 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.

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.

JSON is where it matters most

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.

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.

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.

Validation has to apply everywhere a value can change

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.

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.

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.

The SDK is the last line, not the first

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.

That fallback is the right behavior to keep the application from failing, but it should not be a silent 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.

So the SDKs report the reason for every fallback in their telemetry, and ConfigDirector watches for patterns to alert on:

  • Type mismatches. 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.
  • In-code default values that are not part of the targeting rules. 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 very likely that the in-code default is outdated. You get notified as soon as the drift is detected, before that outdated fallback value becomes a problem.

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.

Try it

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.

The docs on JSON Schema validation cover the limits and the details. The Free plan has no card and includes all of it.