Cloudflare Workers.
Deploy an integration Worker into your own account and evaluate over a service binding.
@splitch/cloudflare is a Worker you deploy into your own Cloudflare account, backed by a SQLite Durable Object. Configuration is pushed to it from splitch, and your application Workers evaluate Flags and live Experiments against it through a service binding, so no evaluation crosses the public Internet. One deployment is bound to one splitch Environment.
Use this when you already run on Workers and want local latency and durable Assignment state. A Worker that is happy with a network round-trip can use the ordinary server client instead.
Setup
You need Wrangler, an authenticated Cloudflare account, an application wrangler.jsonc, and the Environment's API Key exported as SPLITCH_API_KEY. The name is exact; setup reads no other variable. Setup runs the project's own Wrangler if there is one and otherwise the one on your PATH, so npm, pnpm, yarn, bun, and a global install all work; it checks the Wrangler major itself and names the one it wants if yours is wrong.
npm install @splitch/cloudflare @splitch/cli wrangler
export SPLITCH_API_KEY=sk_...
npx splitch cloudflare setup --env productionThe command fails before it mutates anything if the API Key, the application wrangler.jsonc, the Wrangler session, or the Cloudflare account is unavailable. Once past that gate it writes .splitch/cloudflare/production/wrangler.jsonc, generates an installation id and integration secret, deploys the Worker as splitch-config-production, stores SPLITCH_API_KEY and SPLITCH_PUSH_SECRET as Wrangler secrets, registers the deployment with splitch, waits until the pushed configuration version is applied, and adds the SPLITCH service binding to your application's Wrangler environment.
The binding goes into the Wrangler environment named like the splitch Environment key (env.production.services above), or into top-level services when the config declares no env block. When the names differ, pass --wrangler-env: splitch cloudflare setup --env dev --wrangler-env preview deploys splitch-config-dev and binds it in env.preview. A named Wrangler environment must exist; setup never falls back to top-level services. The state file records the binding, so status, remove, and reruns use it, and a rerun with a different --wrangler-env fails instead of moving the binding. remove still works after that Wrangler environment is deleted or the config gains an env block. It refuses, naming each location, while any other binding still points at the integration Worker. When the recorded binding no longer matches the config, setup and status fail and tell you to run remove first.
Setup then regenerates your App's types without --env, passing the application config and every integration config it binds, relative to the App: wrangler types --config wrangler.jsonc --config .splitch/cloudflare/production/wrangler.jsonc. Every Wrangler environment keeps its interface, and a binding only some environments declare stays optional. Setup reports that exact command as typesCommand; put its --config arguments in your App's own types script, or the next plain wrangler types turns SPLITCH back into an untyped Fetcher. Commit .splitch/cloudflare/<env>/wrangler.jsonc and worker.ts so CI generates the same types; only state.json stays ignored. remove regenerates types only when it deleted a binding.
The local state file is written mode 0600 and excluded through .gitignore. An exact rerun discovers and repairs the existing installation. Reusing the Environment name with a different API Key, Cloudflare account, endpoint, or secret fails IDEMPOTENCY_KEY_CONFLICT rather than quietly rebinding it.
Evaluate from your Worker
wrangler types makes env.SPLITCH a typed service binding. The RPC surface is three methods; the same call handles ordinary Flags, Targeting Rules, baseline rollouts, live Experiments, and holdover replay.
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const enabled = await env.SPLITCH.evaluate("new-checkout", {
targetingKey: userId,
idempotencyKey: crypto.randomUUID(),
defaultValue: false,
});
return new Response(enabled ? "new" : "current");
},
};| Method | Returns | Fires an Exposure |
|---|---|---|
evaluate | the Variant value | yes |
evaluateDetails | full ResolutionDetails | yes |
status | installation and delivery health | no |
targetingKey and idempotencyKey are required; idType defaults to user, attributes to {}, and defaultValue to false. Both evaluation methods are Exposure-bearing, because an application RPC is an explicit encounter. There is no remote, experiment, sendExposure, or fallback option. Health checks use status, never an evaluation accessor.
Retrying with the same idempotencyKey and identical input returns the stored result and creates no second Exposure. Reusing that key with different input fails loud rather than serving the stale answer.
Before the first push lands
A Worker that has not yet received its first configuration push resolves to your defaultValue with errorCode: "PROVIDER_NOT_READY". That is the one code the Cloudflare surface adds to the shared catalog, and it is reported rather than disguised: setup waits for the applied version precisely so your first real request is not the one that discovers it.
Operating it
splitch cloudflare status --env productionreports the Worker name, endpoint, installation state, current and applied Environment version, pending delivery count, and the latest bounded error.splitch cloudflare remove --env productionrevokes splitch delivery first, then removes the service binding and the integration Worker. It never deletes an unrelated Worker or an untracked file.
Targeting keys are hashed with an installation-local key held only in Durable Object storage. A raw Targeting Key exists in a pending Exposure row only until that row is accepted, terminal, or 24 hours old.
splitch