Do I need a user ID to evaluate a feature flag?

Last updated:

Not for on/off flags or targeting rules that match on other attributes. You need one for percentage rollouts, because the bucket comes from hashing the identifier with a per-flag salt. Without it the rollout skips that caller, who gets the next matching rule or the flag's default variation. The hosted evaluation endpoint and every server SDK behave the same way.

A flag that is simply on or off needs no context at all, and neither does a targeting rule keyed on something else, like a plan tier or a region. Where the identifier earns its keep is in deciding which side of a percentage split a given caller lands on.

Why the identifier is what makes a rollout stick

Bucketing hashes the user identifier together with a stable per-flag salt, and the result is a number from 0 to 99. Feed it the same inputs and you get the same number every time, which is what stops a user flipping between variations on refresh, across devices, or between API calls.

Take the identifier away and nothing stable is left to hash.

What actually happens without one

The rollout leaves that caller out. A context with no identifier, or an empty one, can’t be bucketed, so the percentage rule doesn’t apply to it. Evaluation carries on with the next rule, and when nothing else matches the caller gets the flag’s default variation.

The hosted evaluation endpoint and every server SDK work this way, so the answer doesn’t depend on where the flag is evaluated. Server SDKs also log a warning once per flag and rule, which makes a missing identifier easy to spot. Browser and mobile SDKs generate and persist an anonymous ID for you, so their visitors are always bucketed.

What to do for logged-out users

If your server already has a stable anonymous ID, from a cookie your app sets for instance, you don’t need to touch the identifier. Set the rollout rule’s Bucket by to that attribute and send it in every context, signed in or not. Featureflip then hashes the anonymous ID, so a visitor keeps their bucket through sign-in as well. Rollout strategies covers the setting.

Without one, generate a key and pass it as the identifier. A UUID in a cookie or in local storage is enough:

let id = localStorage.getItem('ff-anon-id');
if (!id) {
id = crypto.randomUUID();
localStorage.setItem('ff-anon-id', id);
}
const client = FeatureflipClient.get({
clientKey: 'sdk_client_a1b2c3d4',
context: { user_id: id },
});

Now the anonymous visitor buckets like any other user and stays put. Swap the generated key for the real user ID once they sign in, and be aware that the bucket may change at that moment, because the hash input changed. For a checkout experiment that is usually fine. For anything where crossing the boundary mid-session would be jarring, hold the anonymous key until the session ends.

How much anonymity you keep is your call

Supplying a key brings a visitor into the rollout and makes the bucket stick at any point, so the decision is about how much you want to track rather than about a limit in the flag system.

The anonymous case is written up alongside the rest of the bucketing mechanics in Rollout strategies.

Still stuck?

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