How it works
Config file as source of truth, deterministic assignment, exposures and goals, self-hosted vs cloud.
Four ideas explain the whole system.
The config file is the source of truth
splithub.config.ts declares goals and experiments. It is committed, reviewed and diffed like the rest of the code. The CLI edits it (create, start, pause, finish), the SDK reads it, and in cloud mode push uploads a normalized copy so the dashboard and the API can read the same thing.
The dashboard never edits the file. Experiments it creates are "cloud-only" until npx splithub pull writes them into the file; experiments the file owns are read-only in the dashboard except for pausing and finishing. Live overrides (status, traffic, weights, value payloads) sit on top of the config. The browser SDK applies them from a first visit on: it waits briefly for them when nothing is cached and refreshes them in the background on later pages. It never changes a variant a page has shown.
Assignment is a local hash
A visitor gets a random id in the sh_vid cookie (first party, set by the SDK or the Next.js middleware). Assignment uses MurmurHash3 with the visitor id, experiment key and seed, then selects from the cumulative variant weights. A separate hash controls entry into the traffic share. Given the same config and targeting context, browser, edge and server agree. Bucketing is local; fetching cloud overrides or hosted script config may require a request.
Consequences: changing seed reshuffles every visitor (never do it on a running test); changing traffic does not; a visitor keeps the same variant across pages and sessions in the same browser, while the id and experiment settings remain unchanged.
Exposures and goals
An exposure is "this visitor saw this experiment in this variant". The SDK records it once per visitor, experiment and session when the variant is actually rendered (<Experiment> mounts, useValue is read, the redirect fires). A goal is the thing you want more of: an event you call track() for, a pageview of a path, or a click on a selector. Goals are tracked for every visitor; attribution happens at analysis time: a visitor converts for a variant when they sent the goal at or after their first exposure to that variant. Units are unique visitors, not hits.
In the cloud only exposures of running experiments are billed. Goals never are. Exposures for paused, finished or draft experiments are refused at the edge and not stored.
Self-hosted or cloud, same everything
| Self-hosted | SplitHub Cloud | |
|---|---|---|
| Where events go | your database through a Store (SQLite, D1, Postgres, MySQL, custom) |
https://go.splithub.io |
| Config line | storage: { mode: 'self', endpoint: '/api/splithub' } |
storage: { mode: 'cloud', key: 'pk_…' } |
| Results | npx splithub results against your app's endpoint |
CLI, MCP, dashboard, email |
| Live overrides | no (edit the file) | yes |
| Price | free, unlimited | 50k exposures / project / month free, then packs or Pro |
The SDK, the statistics engine (@splithub/stats), the CLI and the MCP server are identical in both modes. To switch, update storage and configure the destination: a handler and database for self-hosting, or a project, keys and config push for cloud.
Vocabulary
- experiment: one question with 2+ variants;
kindisvalue,componentorredirect. - control: the first declared variant, what exists today.
- weight: relative share of traffic per variant (50/50, 1/1, 90/10).
- traffic: share of visitors who enter the experiment at all (
0.5= half; the rest see the control and record no exposure). - verdict: the one-line conclusion of the statistics, with its status code.
- SRM: sample ratio mismatch, the split you got differs from the one you configured; the setup is broken.