Docs / Start View as Markdown

splithub.config.ts

Every field of the config: storage, goals, experiments, variants, traffic, schedule, targeting.

The config is a plain TypeScript (or JavaScript) module that exports defineConfig(...) from @splithub/core. defineConfig validates and normalizes it at import time, so mistakes fail fast with a readable message. The file ships to the browser: never put secrets in it.

import { defineConfig } from '@splithub/core';

export default defineConfig({
  project: 'my-app',
  storage: { mode: 'cloud', key: 'pk_live_…' },
  runDraftsInDev: true,
  goals: {
    signup: { type: 'event' },
    purchase: { type: 'event', revenue: true },
    pricing_view: { type: 'pageview', path: '/pricing' },
    cta_click: { type: 'click', selector: '#cta' },
  },
  experiments: {
    'hero-title': {
      kind: 'value',
      status: 'running',
      goal: 'signup',
      description: 'Feature-led vs outcome-led headline',
      variants: {
        control: { weight: 50, value: 'Ship experiments from your terminal' },
        outcome: { weight: 50, value: 'Know which version wins, by Friday' },
      },
    },
    'pricing-cta': {
      kind: 'component',
      status: 'draft',
      goal: 'purchase',
      traffic: 0.5,
      variants: { control: 1, 'see-plans': 1, 'talk-to-us': 1 },
      maxExposures: 20000,
      schedule: { startAt: '2026-11-01T00:00:00Z', endAt: '2026-11-30T00:00:00Z' },
    },
    'landing': {
      kind: 'redirect',
      status: 'running',
      goal: 'signup',
      match: '/',
      variants: { control: { weight: 50, to: '/' }, 'new-landing': { weight: 50, to: '/start' } },
    },
  },
});

Top level

Field Required Meaning
project yes Name shown in the CLI and the dashboard.
storage yes { mode: 'self', endpoint } or { mode: 'cloud', key, endpoint? }.
goals no Map of goal key → goal.
experiments yes Map of experiment key → experiment.
visitorKey no Cookie name for the visitor id, default sh_vid.
runDraftsInDev no true serves draft experiments like running ones while the app runs in development. Default false.

storage.endpoint in self mode is the path (or URL) where the @splithub/node handler is mounted. In cloud mode key is the project's public key; endpoint defaults to https://go.splithub.io/v1/events and only changes for a self-hosted cloud or staging.

Drafts in development

A draft experiment is invisible to visitors: everyone sees the control and nothing is recorded. To see it in your own app before you start it, set runDraftsInDev: true. While NODE_ENV is development (next dev, vite), drafts behave exactly like running experiments: variants by visitor, targeting, redirects, exposures and goals. A production build ignores them as before, so the same config is safe to deploy.

In development the schedule of a draft is ignored, so a test that starts next week can be checked today. In cloud mode development never sends events to the cloud, for drafts and running tests alike, so your own visits never reach the results and nothing is billed. Deployed builds, and vite preview, send as usual. While no experiment in the project is running the SDK holds goals back too, because the cloud would drop them.

In development the SDK also explains itself, one line per experiment: a draft that everyone sees as the control, a draft shown because of this option and which variant you got, a paused test, events held back. Next.js server code prints it in the terminal that runs next dev. In a Vite app the SDK runs in the browser, so add the plugin to see it in the terminal that runs vite:

// vite.config.ts
import { splithub } from '@splithub/core/vite';

export default defineConfig({ plugins: [react(), splithub()] });

On start and on every save of the config it prints each experiment and what it does in development, then forwards the SDK's notes from the browser. Without the plugin the notes stay in the browser console. Production builds print nothing. With ?sh_debug=1 the debug panel shows such assignments with the reason dev; its "new visitor" button lets you walk through every variant. npx splithub init turns the option on for new projects.

Goals

Type Fields Fires when
event revenue?: boolean your code calls track('<key>', props?); with revenue: true the props.revenue number is summed per variant
pageview path a page with that path is viewed (exact match, or prefix with a trailing *)
click selector an element matching the CSS selector is clicked (the SDK and the drop-in script bind it)

Add description to any goal; the dashboard shows it. Goals created in the dashboard reach cloud clients. Add their definitions to the local file manually; pull adopts experiments, not goals.

Experiments

Keys are lowercase a-z 0-9 - _, 1–64 characters. The first variant is the control.

Field Default Meaning
kind required value, component or redirect
status required draft, running, paused, finished
variants required see below
goal none the goal the verdict is computed on; must exist in goals
traffic 1 share of visitors (0–1) entering the experiment; others see the control without an exposure
seed the key hash seed; never change it on a running experiment
description none shown in the dashboard and in emails
match required for redirect path the redirect applies to (exact, or prefix with trailing *)
maxExposures none the cloud finishes the experiment when stored exposure events reach this number
schedule none { startAt?, endAt? } ISO dates; outside the window the control is served and nothing is recorded
flow none targeting graph, see Targeting flows

Variants by kind

// component: weights only, JSX lives in the app
variants: { control: 1, bold: 1 }

// value: weight + payload (string, number, boolean, object, array)
variants: { control: { weight: 50, value: 'Old' }, bold: { weight: 50, value: 'New' } }

// redirect: weight + destination
variants: { control: { weight: 50, to: '/' }, v2: { weight: 50, to: '/home-v2' } }

Weights are relative: { a: 1, b: 1 } and { a: 50, b: 50 } are the same split; { a: 90, b: 10 } is a 90/10 split.

What the CLI changes

create adds an experiment, start/pause/finish change status, pull adds missing dashboard-created experiments (including flows), but not goal definitions, init writes the file the first time. Edits keep your formatting where possible; the file stays a normal TypeScript module you can edit by hand.

Rules

  • Never change seed, variants or weights of a running experiment; create a new one.
  • Keep one id for things that must change together, separate ids for independent questions.
  • On low traffic run at most 2–3 experiments per page.
  • Keys that exist in the file are owned by the file: the dashboard cannot edit them (it can pause or finish them).
  • In cloud mode a status set in the dashboard wins over the file's status, from a visitor's first page, until you clear it. The dashboard shows both when they differ.
SplitHub
Test ideas. Ship winners.