OpenFeature provider for the Spotify Confidence resolver (local mode, powered by WebAssembly). It periodically fetches resolver state, evaluates flags locally, and flushes evaluation logs to the Confidence backend.
- Local flag evaluation via WASM (no per-eval network calls)
- Automatic state refresh and batched flag log flushing
- Pluggable
fetchwith retries, timeouts and routing - Optional logging using
debug - React integration for Next.js with Server Components
- Node.js 18+ (built-in
fetch) or provide a compatiblefetch - WebAssembly support (Node 18+/modern browsers)
yarn add @spotify-confidence/openfeature-server-provider-local
# Optional: enable logs by installing the peer dependency
yarn add debugNotes:
debugis an optional peer. Install it if you want logs. Without it, logging is a no-op.- Types and bundling are ESM-first; Node is supported, and a browser build is provided for modern bundlers.
You'll need a client secret from Confidence to use this provider.
📖 See the Integration Guide: Getting Your Credentials for step-by-step instructions on:
- How to navigate the Confidence dashboard
- Creating a Backend integration
- Creating a test flag for verification
- Best practices for credential storage
The provider supports encrypting the flag state to protect your flag rules and targeting segments at rest and in transit. The state is decrypted only when loaded into the resolver.
📖 See the Integration Guide: Encryption for background and migration details.
Pass the encryption key when creating the provider:
const provider = createConfidenceServerProvider({
flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!,
encryptionKey: process.env.CONFIDENCE_ENCRYPTION_KEY!,
});The encryption key is available in the Confidence Admin view, next to your client credentials.
⚠️ Upcoming change: Encryption will be made mandatory in a future SDK release. We will communicate a timeline and migration path before legacy provider versions are affected. We strongly recommend enabling it now.
import { OpenFeature } from '@openfeature/server-sdk';
import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local';
const provider = createConfidenceServerProvider({
flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!,
// initializeTimeout?: number
// stateUpdateInterval?: number
// flushInterval?: number
// fetch?: typeof fetch (Node <18 or custom transport)
});
// Wait for the provider to be ready (fetches initial resolver state)
await OpenFeature.setProviderAndWait(provider);
const client = OpenFeature.getClient();
// Create evaluation context with user attributes for targeting
const context = {
targetingKey: 'user-123',
country: 'US',
plan: 'premium',
};
// Evaluate a boolean flag
const enabled = await client.getBooleanValue('test-flag.enabled', false, context);
console.log('Flag value:', enabled);
// Evaluate a nested value from an object flag using dot-path
// e.g. flag key "experiments" with payload { groupA: { ratio: 0.5 } }
const ratio = await client.getNumberValue('experiments.groupA.ratio', 0, context);
// On shutdown, flush any pending logs
await provider.onClose();The evaluation context contains information about the user/session being evaluated for targeting and A/B testing.
// Simple attributes
const context = {
targetingKey: 'user-123',
country: 'US',
plan: 'premium',
age: 25,
};The provider uses a default value fallback pattern - when evaluation fails, it returns your specified default value instead of throwing an error.
📖 See the Integration Guide: Error Handling for:
- Common failure scenarios
- Error codes and meanings
- Production best practices
- Monitoring recommendations
// The provider returns the default value on errors
const enabled = await client.getBooleanValue('my-flag.enabled', false, context);
// enabled will be 'false' if evaluation failed
// For detailed error information, use getBooleanDetails()
const details = await client.getBooleanDetails('my-flag.enabled', false, context);
if (details.errorCode) {
console.error('Flag evaluation error:', details.errorMessage);
console.log('Reason:', details.reason);
}flagClientSecret(string, required): The flag client secret used during evaluation and authentication.encryptionKey(string, optional): Encryption key for decrypting the flag state. Found in the Confidence Admin view. Will be required in a future release (see Encryption).initializeTimeout(number, optional): Max ms to wait for initial state fetch. Defaults to 30_000.stateUpdateInterval(number, optional): Interval in ms between state polling updates. Defaults to 30_000.flushInterval(number, optional): Interval in ms for sending evaluation logs. Defaults to 10_000.fetch(optional): Customfetchimplementation. Required for Node < 18; for Node 18+ you can omit.
The provider periodically:
- Refreshes resolver state (configurable via
stateUpdateInterval, default every 30s) - Flushes flag evaluation logs to the backend (configurable via
flushInterval, default every 10s)
The package provides multiple exports for different environments:
import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local';The WASM is inlined as a data URL — this is the most portable option and should work across virtually all environments. The tradeoff is a larger bundle (~700kB), but this isn't a problem for the intended server-side usage.
No configuration needed.
import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local/node';Uses fs.readFile() to load WASM from the installed package. Works well in a regular Node.js environment with node_modules.
You can customize the WASM path if needed:
const provider = createConfidenceServerProvider({
flagClientSecret: '...',
wasmPath: '/custom/path/to/confidence_resolver.wasm',
});import { createConfidenceServerProvider } from '@spotify-confidence/openfeature-server-provider-local/fetch';Uses fetch() with import.meta.url to load WASM. Works in Deno, Bun, and browsers with bundlers that properly handle asset URLs (Vite, Rollup, etc.).
You can customize the WASM URL if needed:
const provider = createConfidenceServerProvider({
flagClientSecret: '...',
wasmUrl: '/assets/confidence_resolver.wasm',
});// Server Component
import { ConfidenceProvider, getFlag, getFlagDetails } from '@spotify-confidence/openfeature-server-provider-local/react-server';
// Client Component
import { useFlag, useFlagDetails } from '@spotify-confidence/openfeature-server-provider-local/react-client';React hooks and components for Next.js App Router with Server Components. Flags are resolved on the server and provided to client components via React Context.
See README-REACT.md for full documentation.
While browsers are mentioned in this doc, this package is intended for server-side use only. Two concerns for browser usage:
- Size: The WASM+JS is currently ~270kb gzipped, too large for typical client bundles.
- Security: In a browser, all flag rules and variants are exposed to users.
That said, the package does work in browsers, and there may be specialized use cases where these tradeoffs are acceptable.
Materialization stores provide persistent storage for sticky variant assignments and custom targeting segments. This enables two key use cases:
-
Sticky Assignments: Maintain consistent variant assignments across evaluations even when targeting attributes change. This enables pausing intake (stopping new users from entering an experiment) while keeping existing users in their assigned variants.
-
Custom Targeting via Materialized Segments: Precomputed sets of identifiers from datasets that should be targeted. Instead of evaluating complex targeting rules at runtime, materializations allow efficient lookup of whether a unit (user, session, etc.) is included in a target segment.
MaterializationStore (e.g., Redis, DynamoDB, or a key-value store) to avoid unexpected fallbacks and ensure consistent variant assignment.
For quick setup without managing your own storage infrastructure, enable the built-in remote materialization store:
const provider = createConfidenceServerProvider({
flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!,
materializationStore: 'CONFIDENCE_REMOTE_STORE',
});When to use:
- You need sticky assignments or materialized segments but don't want to manage storage infrastructure
- Quick prototyping or getting started
- Lower-volume applications where network latency is acceptable
Trade-offs:
- Additional network calls during flag resolution (adds latency)
- Lower performance compared to local storage implementations (Redis, DynamoDB, etc.)
For improved latency and reduced network calls, implement the MaterializationStore interface to store materialization data in your infrastructure:
import { MaterializationStore } from '@spotify-confidence/openfeature-server-provider-local';
class MyRedisStore implements MaterializationStore {
async readMaterializations(readOps: MaterializationStore.ReadOp[]): Promise<MaterializationStore.ReadResult[]> {
// Load materialization data from Redis
}
async writeMaterializations(writeOps: MaterializationStore.WriteOp[]): Promise<void> {
// Store materialization data to Redis
}
}
const provider = createConfidenceServerProvider({
flagClientSecret: process.env.CONFIDENCE_FLAG_CLIENT_SECRET!,
materializationStore: new MyRedisStore(),
});For read-only stores (e.g., pre-populated materialized segments without sticky assignment writes), omit the writeMaterializations method.
Consider implementing a materialization store if:
- You need to support sticky variant assignments for experiments
- You use materialized segments for custom targeting
- You want to minimize network latency during flag resolution
- You have high-volume flag evaluations
If you don't use sticky assignments or materialized segments, the default behavior is sufficient.
Logging uses the debug library if present; otherwise, all log calls are no-ops.
Namespaces:
- Core:
cnfd:* - Fetch/middleware:
cnfd:fetch:*(e.g. retries, auth renewals, request summaries)
Log levels are hierarchical:
cnfd:debugenables debug, info, warn, and errorcnfd:infoenables info, warn, and errorcnfd:warnenables warn and errorcnfd:errorenables error only
Enable logs:
- Node:
DEBUG=cnfd:* node app.js
# or narrower
DEBUG=cnfd:info,cnfd:fetch:* node app.js- Browser (in DevTools console):
localStorage.debug = 'cnfd:*';Install debug if you haven’t:
yarn add debug- You can inject a custom
fetchvia thefetchoption to stub network behavior in tests. - The provider batches logs; call
await provider.onClose()in tests to flush them deterministically.
By default, every flag evaluation triggers an exposure event (apply). If you need to resolve a flag without recording an exposure, you can pass _confidence_skip_apply: true in the evaluation context:
const value = await client.getBooleanValue('my-flag.enabled', false, {
targetingKey: 'user-123',
_confidence_skip_apply: true,
});The key is automatically stripped from the context before it reaches the resolver.
This is an advanced feature intended for specific use cases such as prefetching or background evaluation. If you're considering using it, reach out to the Confidence team to discuss the best approach for your setup.
See the root LICENSE.
Code is formatted using prettier, you can format all files by running
yarn format