VersuchClient
Browser / mobile. Uses a public key (vpk_live_… or csk_live_…). Receives a pre-evaluated payload, no targeting rules. Browser SDK →
@versuch/sdk is the official SDK for Versuch feature flags. It does local caching, local evaluation, exposure tracking, and has a plugin system, for browsers, Node, Bun, Deno, and edge runtimes (Cloudflare Workers and others).
npm install @versuch/sdk| Import | Exports | Use in |
|---|---|---|
@versuch/sdk | VersuchClient, types | Browser / public |
@versuch/sdk/server | VersuchServerClient | Server / edge |
@versuch/sdk/react | VersuchProvider, useFlag | React apps |
@versuch/sdk/node | file cache adapter | Node servers |
Each client is paired with a key that is safe where it runs.
VersuchClient
Browser / mobile. Uses a public key (vpk_live_… or csk_live_…). Receives a pre-evaluated payload, no targeting rules. Browser SDK →
VersuchServerClient
Server / edge. Uses a secret key (vsk_live_… or ssk_live_…). Fetches the full config and evaluates locally with rules and segments. Server SDK →
The public key is safe in a browser bundle: the API only returns a sanitized, pre-evaluated payload to it. The clients guard the boundary, the server client throws in a browser, and the browser client throws if handed a secret key.
Available on both clients (the browser client takes no per-call context; the server client takes context per call).
| Method | Returns |
|---|---|
boolVariation(key, [ctx,] default) | boolean |
stringVariation(key, [ctx,] default) | string |
numberVariation(key, [ctx,] default) | number |
jsonVariation(key, [ctx,] default) | object |
variation<T>(key, [ctx,] default) | typed T |
allFlags([ctx]) | every flag resolved |
detail(key, [ctx,] default) | value + reason + version |
Evaluation never throws, a missing flag or a network outage returns the default you passed.
A context is { key, attributes }:
key: the stable bucketing unit; deterministic assignment keys off it.attributes: the fields targeting rules reference (plan, country, beta_user, …).The browser client holds one context at a time (set at init, changed with identify()); the server client takes context per evaluation call. See flag concepts.
| Option | Default | Notes |
|---|---|---|
environment | "production" | Which env’s config to read |
baseUrl | https://api.versuch.ai | |
cache | auto | CacheAdapter, or false to disable persistence |
pollIntervalMs | 60000 client / 30000 server | 0 disables |
sendExposures | true | Record exposures for analytics |
fetch | global fetch | Required on Node 18 and earlier |
timeoutMs | 8000 | |
maxRetries | 2 | |
logger | off | true for console, or a Logger |
plugins | [] | |
offline | false | Never hit the network |
The SDK caches the config payload so evaluation is instant on subsequent loads:
localStorage. A returning visitor evaluates on the first line, before any request completes; fresh config polls in the background.CacheAdapter to persist anywhere (a file cache ships under @versuch/sdk/node).import type { CacheAdapter } from "@versuch/sdk";
const redisCache: CacheAdapter = { get: (k) => redis.get(k), set: (k, v) => redis.set(k, v),};Updates arrive via polling (client ~60s, server ~30s) or SSE (stream: true). Both use the cheap GET /v1/config/version poll to avoid re-fetching unchanged config.
Observe evaluations, config changes, and errors by passing plugins:
new VersuchClient({ clientKey, plugins: [ { name: "logger", onEvaluation: (d) => console.log(d.flagKey, "→", d.value), onConfigChange: (c) => console.log("published v" + c.toVersion), }, ],});Other extension points: fetch (custom transport/proxy), logger, pollIntervalMs, timeoutMs, maxRetries, and offline (serve only cache/bootstrap, great for tests and SSR).
Every variation() records an exposure (which context saw which variation) and batches it for experiment analytics. Only the context key is sent, the server hashes it, attributes are never included. Browser exposures post to /v1/exposures; the server client batches and flushes. Disable with sendExposures: false.
All failures are a VersuchError with a stable .code:
UNAUTHORIZED, NOT_CONFIGURED, NETWORK, TIMEOUT, PARSE, CACHE, MISUSE, CONFIG.
import { VersuchError } from "@versuch/sdk";
try { await client.init();} catch (err) { if (err instanceof VersuchError && err.code === "UNAUTHORIZED") { // wrong key or environment }}Evaluation itself never throws, only setup/transport paths do.
Browser SDK
Server SDK
React
Targeting