Using the Core Library
The @vercel/flags-core library provides direct access to the Vercel Flags evaluation engine. Use it when you need full control over flag evaluation or are working outside of supported frameworks.
- Building custom tooling or CLI applications
- Working with frameworks other than Next.js or SvelteKit
- Creating server-side applications without a web framework
- Building custom integrations or adapters
pnpm i @vercel/flags-coreyarn add @vercel/flags-corenpm i @vercel/flags-corebun add @vercel/flags-coreUse the default client when your code runs on Vercel. It authenticates with Vercel OpenID Connect (OIDC) and needs no configuration:
import { flagsClient } from '@vercel/flags-core';
const result = await flagsClient.evaluate('flag-name', false);If the FLAGS environment variable is set, the default client uses that SDK key instead of OIDC.
To pass client options, create your own client with createClient(). Without an argument, it authenticates with OIDC. It doesn't read the FLAGS environment variable:
import { createClient } from '@vercel/flags-core';
const client = createClient();Use an SDK Key when your code runs outside Vercel, such as on a custom backend, or when it reads flags from another project. Pass the key to createClient():
import { createClient } from '@vercel/flags-core';
const client = createClient(process.env.FLAGS_SDK_KEY);On Vercel, the OpenID Connect (OIDC) token can come from the current request context. The token might not be available while modules load. Create the client at module scope, but call evaluate() or bulkEvaluate() inside a request handler. These methods initialize the client when you first call them.
Do not call initialize() at module scope or store a module-scoped initialization promise when you use request-scoped OIDC. Awaiting an already-started promise inside a request handler does not move that work into the request context. This also applies to embedded definitions because the client uses the token's project_id claim to select the correct definitions.
Local credentials can hide this timing requirement. For example, vercel env pull writes an OIDC token to .env.local. Keep evaluation and explicit initialization inside the request handler so the same code works locally and in a deployment.
createClient accepts an optional second argument to configure how the client fetches and updates flag definitions. Pass undefined as the first argument to keep the default Vercel OIDC authentication:
import { createClient } from '@vercel/flags-core';
const client = createClient(undefined, {
stream: { initTimeoutMs: 5000 },
polling: { intervalMs: 60000, initTimeoutMs: 10000 },
});| Option | Type | Default | Description |
|---|---|---|---|
datafile | DatafileInput | - | An initial datafile for immediate reads without waiting for a network request. |
stream | boolean | { initTimeoutMs: number } | true | Enable streaming updates via SSE. Set initTimeoutMs to control how long to wait for the first update. |
polling | boolean | { intervalMs, initTimeoutMs } | true | Enable polling as a fallback. intervalMs controls refresh frequency, initTimeoutMs controls the initial wait. |
buildStep | boolean | Auto-detected | Override build step auto-detection. See Data source fallback chain. |
waitUntil | (promise: Promise<unknown>) => void | Runtime-specific | Keep background usage and exposure reporting alive until the supplied promise settles. |
The Next.js export uses after from next/server as its waitUntil default. Other runtimes use waitUntil from @vercel/functions. Pass your platform's lifecycle function when it does not support either default. An explicit waitUntil option always takes precedence.
The client initializes automatically when you call evaluate() or bulkEvaluate(). Call initialize() explicitly only when you need to load definitions and handle initialization errors before evaluation:
await client.initialize();The client caches flag definitions in memory and keeps them up to date using streaming and polling mechanisms.
Use the evaluate method to get a flag's value:
const result = await client.evaluate<boolean>('show-new-feature', false);
if (result.value) {
// Show the new feature
}The second argument is the default value, returned when the flag doesn't exist or evaluation fails.
Pass an evaluation context to use targeting rules:
const result = await client.evaluate<boolean>('premium-feature', false, {
user: {
id: 'user-123',
email: 'user@example.com',
plan: 'premium',
},
});
console.log(result.value); // true or false based on targeting rulesThe context structure should match the entities you've defined in the Vercel Dashboard.
The evaluate method returns a result object with detailed information:
const result = await client.evaluate<string>('theme', 'light');
console.log({
value: result.value, // The evaluated value
reason: result.reason, // Why this value was returned
errorMessage: result.errorMessage, // Error details if applicable
});The reason field indicates how the value was determined:
import { Reason } from '@vercel/flags-core';
switch (result.reason) {
case Reason.TARGET_MATCH:
// A specific target matched
break;
case Reason.RULE_MATCH:
// A targeting rule matched
break;
case Reason.FALLTHROUGH:
// No rules matched, using default
break;
case Reason.PAUSED:
// Flag is paused/disabled
break;
case Reason.ERROR:
// Evaluation failed
console.error(result.errorMessage);
break;
}The client uses a fallback chain to resolve flag definitions. The chain differs depending on whether the client is running at build time or at runtime.
During a build step (detected when CI=1 or NEXT_PHASE=phase-production-build, or when buildStep: true is set), the client avoids network connections and resolves definitions in this order:
- Provided datafile: Uses the
datafileoption if provided - Embedded definitions: Uses definitions embedded at build time
- Fetch: Last resort network fetch
At runtime (the default, or when buildStep: false is set), the client uses real-time mechanisms first and falls back to static sources:
- Stream: Real-time updates via SSE, waits up to
initTimeoutMs(default: 3000ms) - Polling: Interval-based HTTP requests, waits up to
initTimeoutMs(default: 3000ms) - Provided datafile: Uses the
datafileoption if provided - Embedded definitions: Uses definitions embedded at build time
Key behaviors:
- The client never streams and polls at the same time
- If the stream disconnects, the client starts polling (if enabled)
- If the stream reconnects while polling, polling stops
- If in-memory data already exists, the client serves it immediately while background updates happen
Use the buildStep option to explicitly control which fallback chain the client uses:
// Force build step mode (skip network connections)
const client = createClient(undefined, {
buildStep: true,
});
// Force runtime mode (use streaming and polling)
const client = createClient(undefined, {
buildStep: false,
});This is useful when auto-detection doesn't match your environment. If you pass custom logic, ensure that buildStep is true during the build phase but false at runtime.
When you deploy to Vercel, the build process fetches your latest flag definitions once at build time and bundles them into the deployment. This happens automatically when the project uses @flags-sdk/vercel or @vercel/flags-core and the build can authenticate with Vercel OIDC or an SDK Key. This serves two purposes:
- Build consistency: Every function in the build uses the same snapshot of flag definitions, fetched once at the start of the build. Without embedding, each function may fetch definitions independently, which could lead to inconsistent behavior if definitions change mid-build.
- Runtime resilience: If the Vercel Flags service is temporarily unreachable at runtime, the SDK falls back to the embedded snapshot instead of returning hardcoded default values. Because the snapshot preserves your full configuration, including targeting rules, segments, and percentages, your flags continue to evaluate accurately. Since the snapshot is from build time, users may see slightly outdated values until the service recovers.
Because the flag definitions are bundled into your deployment, they count toward the function bundle size limit.
When your application exits, shut down the client to clean up resources:
await client.shutdown();This outputs detailed information about the client's data source connections, fallback behavior, and evaluation steps.
- Learn about the Flags SDK for framework-native integration
- Use OpenFeature for a vendor-neutral API
- Configure entities for targeting rules
Was this helpful?