Why is my feature flag returning the default value?

Last updated:

The SDK almost certainly cannot find the flag at all, so evaluation never got far enough to fail. Check the SDK key first, since it may belong to a different environment, then whether the flag is simply off there. After that comes a misspelled key, a client still initializing, a type mismatch against the variation, or a flag that is not client-side visible when you ask from a browser.

Every variation call takes a default as a required argument. The SDK falls back to it under four conditions: a missing flag, an unexpected value type, a client that has not finished loading, and an evaluation error. That design buys you an outage which cannot take your app down. The cost is that a configuration mistake and a healthy fallback produce identical output.

Work through these in order. The first two account for most reports.

1. The SDK key belongs to a different environment

Each environment has its own SDK key, and the key decides which configuration your process receives. A production key will never see a flag you only created in development. Nothing errors, because from the SDK’s point of view the flag genuinely does not exist.

Print the key prefix your process actually loaded:

console.log(process.env.FEATUREFLIP_SDK_KEY?.slice(0, 18));
// sdk_server_a1b2c3 -> server key
// sdk_client_a1b2c3 -> client key

Then confirm in the dashboard that the key belongs to the environment you are looking at. A stale value in a deploy config or a .env file that shadows the real one is the single most common cause.

2. The flag is off in that environment

Flags are configured per environment. On in development and off in production is the normal state during a rollout, and a disabled flag serves its off variation everywhere it is disabled.

3. The key is misspelled

Flag keys are exact strings. new-checkout and new_checkout are two different flags, and only one of them exists. The SDK reports this as flag-not-found and carries on.

4. The client has not finished initializing

Server SDKs load the full configuration once at startup. Read a flag before that completes and you get the default, correctly, because there is nothing else to serve yet.

await client.initialize();
// every variation call after this point reads from memory

In a browser or React app the equivalent is the readiness flag the SDK exposes. Gate on it before you trust a value.

5. The type does not match

Asking for a boolean from a string flag returns the default. No coercion happens. Check that the variation type in the dashboard matches the method you are calling.

6. The flag is not client-side visible

This one only affects browser, React, Flutter, Android and Swift SDKs. A flag has to be explicitly marked client-side visible before a client key can see it, and the SDK reports the same flag-not-found it would for a key that does not exist. There is a separate answer covering exactly that case.

One thing this is usually not

An unmet prerequisite. When a prerequisite is not satisfied the flag serves its off variation, which is a value you configured, rather than the default you passed at the call site. So if the default specifically is what you are seeing, look elsewhere. The reason string separates them cleanly, with prerequisite-failed on one side and flag-not-found on the other.

For what the SDK does under every failure mode, see Reliability and resilience. Cause 1 and cause 2 both come back to Environments, which is the better place to start if the key looks suspect.

Still stuck?

The docs cover every SDK, and the free Solo plan is enough to reproduce most of these locally.