Evaluation Context
Segment by any criteria, using an evaluation context.
It is common for features to be on for some users, but off for others. For example team members working on a new setting might need to see and use the setting, while the rest of the team need the setting to be hidden.
The flag declaration accepts an identify
function. The entities returned from the identify function
are passed as an argument to the decide function.
Example
A trivial case to illustrate the concept:
import { flag } from 'flags/next';
export const exampleFlag = flag<boolean>({
key: 'identify-example-flag',
identify() {
return { user: { id: 'user1' } };
},
decide({ entities }) {
return entities?.user?.id === 'user1';
},
});Having first-class support for an evaluation context allows decoupling the identifying step from the decision making step.
Type safety
The entities can be typed using the flag function.
import { flag } from 'flags/next';
interface Entities {
user?: { id: string };
}
export const exampleFlag = flag<boolean, Entities>({
key: 'identify-example-flag',
identify() {
return { user: { id: 'user1' } };
},
decide({ entities }) {
return entities?.user?.id === 'user1';
},
});Headers and Cookies
The identify function is called with headers
and cookies arguments, which is useful when dealing with
anonymous or authenticated users.
The arguments are normalized to a common format so the same flag to be
used in Routing Middleware, App Router, and Pages Router without having to
worry about the differences in how headers and
cookies are represented there.
import { flag } from 'flags/next';
export const exampleFlag = flag<boolean, Entities>({
// ...
identify({ headers, cookies }) {
// access to normalized headers and cookies here
headers.get('auth');
cookies.get('auth')?.value;
// ...
},
// ...
});Deduplication
The dedupe function is a helper to prevent duplicate work.
Any function wrapped in dedupe will only ever run once for
the same request within the same runtime and given the same arguments.
This helper is extremly useful in combination with the
identify function, as it allows the identification to only
happen once per request. This is useful in preventing overhead when
passing the same identify function to multiple feature
flags.
Learn more about dedupe
Precomputing and targeting
The Marketing Pages example which shows how to identify and target users using cookies when precomputing pages.
Custom evaluation context
While it is best practice to let the identify function
determine the evaluation context, it is possible to provide a custom
evaluation context.
// pass a custom evaluation context from the call side
await exampleFlag.run({ identify: { user: { id: 'user1' } } });
// pass a custom evaluation context function from the call side
await exampleFlag.run({ identify: () => ({ user: { id: 'user1' } }) });This should be used sparsely, as custom evaluation context can make feature flags less predictable across your code base.
Full example
The example below shows how to use the identify function to
display different content to different users.
The above example is implemented using this feature flag:
import type { ReadonlyRequestCookies } from 'flags';
import { dedupe, flag } from 'flags/next';
interface Entities {
user?: { id: string };
}
const identify = dedupe(
({ cookies }: { cookies: ReadonlyRequestCookies }): Entities => {
// This could read a JWT instead
const userId = cookies.get('identify-example-user-id')?.value;
return { user: userId ? { id: userId } : undefined };
},
);
export const identifyExampleFlag = flag<boolean, Entities>({
key: 'identify-example-flag',
identify,
decide({ entities }) {
if (!entities?.user) return false;
return entities.user.id === 'user1';
},
});See the Marketing Pages example