CLI reference
init (websites, React Native, Expo, iOS, Android), create, start/pause/finish, status, results, analytics, push, pull, login, doctor, skills.
The splithub package is the command line for people (prompts, menus) and for agents (flags, --json). It covers analytics and experiments, plus notes, short links, alerts and allowed domains. Run it with npx splithub or install it as a dev dependency.
npx splithub # interactive menu: experiments, analytics, links, alerts, notes, domains
npx splithub --help # every command and flag
Run commands from the app directory. --json prints one line of JSON; errors are { error, code, hint? } with exit code 1.
Experiments
| Command | Does |
|---|---|
init |
Writes splithub.config.ts, Next.js middleware and a self-hosted route where applicable, agent skills and MCP entries. Prints the provider snippet for you to add. --mode cloud signs in through a link; --mode self adds the ingest route and a store. |
create |
Adds an experiment to the config. Wizard without flags; flags below. Scaffolds variant components for component. The goal can be any event you track(). |
start <id> / pause <id> / finish <id> |
Changes status in the file and applies it live in cloud mode (within a minute). The experiment must already be pushed and credentials available. |
status |
Every experiment with status, visitors, days and the verdict headline; in the cloud also each analytics property's last 7 days. |
results <id> |
Full verdict: sample, comparisons, intervals, P(beat), stars, SRM, metrics, factors. Cloud views below. |
events --experiment <id> |
The latest raw exposures and goals (--type exposure|goal, --limit 50, --before <ms>, --from --to, --filter). 7 days of history, 30 on Pro. |
plan |
Visitors an experiment needs, from SplitHub Cloud, no account needed: --baseline 0.05 --mde 0.1 [--variants 2] [<id>] (with an id, the days at its traffic). |
refs <id> |
Files and lines that use the experiment and its goal. |
ship <id> --keep <variant> |
Finishes the experiment, removes it from the config and rewrites the code to the kept variant. --dry-run shows the diff. |
remove <id> |
Removes an experiment from the config (archived in the cloud when connected). |
holdout set <percent> / holdout off |
Keeps a share of visitors (up to 50%) out of every experiment to measure the long-term impact; --goal <event>. |
review [<id>] |
AI reviews stored in the cloud (Pro): at start, on verdict changes, on finish, or on demand. |
create flags
--kind value|component|redirect|mvt --id <key> --goal <event> --status running|draft
--name "Hero headline" --description "..." --yes
--variants control,b --weights 50,50 # value, component, redirect
--value control="Old text" --value b="New text" # value (repeatable)
--match /path --to control=/ --to b=/new # redirect (repeatable)
--factor headline=old:Old,new:New --factor button=blue:#2563eb,green:#16a34a # mvt (first level = control)
--traffic 50 --max-visitors 10000 --min-days 3 --good-sample 5000 --start <ISO> --end <ISO>
--secondary pricing_view,signup --guardrail refunds:higher,errors
--layer checkout:0-50
--traffic is a percent of visitors. --guardrail goal:higher marks a goal where a rise is bad. --layer key:start-end enrolls buckets start to end (end excluded) of a layer; ranges in one layer must not overlap.
Results views (SplitHub Cloud)
npx splithub results hero --from 2026-10-01 --to 2026-10-15 --filter country=DE # a slice
npx splithub results hero --by device # lift per segment
npx splithub results hero --timeline # visitors and conversions per day
npx splithub results hero --engagement # bounce, pages per visit, visit length per variant
npx splithub results hero --events # every goal and event per variant, lift vs control
npx splithub results hero --by country --export > hero.csv
--from / --to are first-exposure days in the project's time zone. Filters and --by dimensions: country, region, city, device, os, browser, language, host, platform, app_version, source, channel, utm_*, landing, page. A slice's verdict covers the slice only and carries no advice. --engagement and the non-goal rows of --events need an analytics property on the same site.
Analytics
| Command | Does |
|---|---|
analytics connect [<id>] |
Picks or creates a property and adds property="sh-…" to the app's provider, or prints the sh.js tag. --name "Site" creates one, --force replaces another property named there. |
analytics |
Every property with visitors, visits, pageviews and bounce for the last 30 days. |
analytics <id> |
One property: totals, users, goals, top pages, top sources, notes. --by page|source|channel|country|event|goal|…, --by variant --experiment <key>, --by trait --key <name>, --days 7, --from --to, --filter country=DE, --compare [year|<from>..<to>], --segment <name>. |
analytics create --name "Site" |
Adds a property (id generated, like sh-x1h78w); in cloud mode live at once, no push. --kind site|app (default: app in an app project) picks a website or an app property. |
analytics pause|resume|archive|restore|remove <id> |
Stops or restarts collecting, archives (cloud), or removes a property. |
analytics events <id> |
The latest raw pageviews and events, to check tracking. |
analytics funnel <id> --steps "page:/,event:Signup,goal:paid" |
Visitors per step, conversion and drop-off (--within visit|7d|30d, --by). |
analytics paths <id> --page /pricing [--prev] |
Where visitors go next, or came from. |
analytics retention <id> |
Who comes back, per cohort (--start visit|event:<name>|goal:<key>, --return visit|event:<name>, --period day|week, --from --to). |
analytics realtime <id> |
Visitors in the last 5 minutes, top pages and sources. |
analytics props <id> --event <name> [--key <prop>] |
An event's prop names, or one prop's values. |
analytics export <id> |
CSV to stdout (--by <dimension>|day, --raw). |
analytics segments |
Saved filters: --save <name> country=DE …, --delete <id|name>. |
properties is an alias of analytics. Self-hosted, analytics reads raw counts from your database; reports with filters, --by, --compare, funnels and retention come with SplitHub Cloud. See Analytics and Reports.
Notes, links, alerts, domains (SplitHub Cloud)
| Command | Does |
|---|---|
notes |
What happened on a day, marked on experiment timelines and analytics charts (--experiment <key> | --property <id>, --kind note|issue|win, --days 30). |
notes add "<text>" |
[--kind issue|win] [--experiment <key> | --property <id>] [--day YYYY-MM-DD]; notes edit <id> "<text>", notes delete <id>. |
links |
Short links and their clicks; links <id|slug> for one link's clicks. |
links create <url> [<url> …] |
[--slug spring] [--name "Spring sale"] [--weights 70,30]; links edit|pause|resume|delete <id|slug>. See Short links. |
alerts |
Alert rules and recent alerts; alerts enable|disable|delete|test <id>. |
alerts add <condition> |
Adds a rule; conditions and flags in Alerts. Rules run on Pro. |
domains |
Which domains may send events, per experiment and property; domains set experiment|property <id> <domain…> (an app id like com.acme.shop works too), domains clear experiment|property <id>. |
Project
| Command | Does |
|---|---|
push [--dry-run] |
Uploads the normalized config to the cloud. The first push returns the public key and writes it into storage.key. Adopts missing dashboard-created experiments into the file. --dry-run shows what would change. A key that belonged to another experiment stops the push: --replace <keys> (new experiment, old numbers cleared) or --same <keys> (same experiment). |
pull |
Shows the config stored in the cloud and writes missing dashboard-created experiments, including their flows, into the file. Existing entries are not overwritten. |
report |
Status and verdicts as Markdown (raw numbers when self-hosted), with an analytics table in the cloud. |
ci init |
Writes .github/workflows/splithub.yml: checks on pull requests, a results comment on a schedule. |
dev [--url] [--hold] |
Opens the app with ?sh_debug=1 (console and panel: variants, reasons, events) and watches results. |
doctor |
Checks config, provider, middleware, route, code references, endpoint reachability, key. |
update [--yes] |
Updates splithub and the project's @splithub/* packages to the latest release. |
skills [--mcp] |
(Re)installs the agent skills; --mcp also writes .mcp.json and .cursor/mcp.json. |
login [--project <name|id>] / logout / whoami |
Connects a folder to a cloud project through a browser approval; shows the project and its public key. |
projects |
Lists local directories connected to cloud projects. |
A push uploads the whole file. An experiment the cloud has from an earlier push that the file lacks is removed; push --dry-run lists it first. Adding analytics never needs a push.
init in detail
npx splithub init --mode cloud --yes # link sign-in, then writes everything
npx splithub init --mode cloud --key pk_live_… --yes # public key known; add --no-login to skip browser sign-in
npx splithub init --mode self --db sqlite # also: postgres | mysql | custom
npx splithub init --no-install --no-login # files only
npx splithub init --github-action # also writes the GitHub workflow
init looks at the project first:
| Project | What init does |
|---|---|
| Next.js, Vite, other React | writes the config and framework files, prints the provider snippet |
| React Native, Expo | installs @splithub/react-native and its storage module (npx expo install on Expo), wraps app/_layout.tsx (Expo Router, adds <ExpoRouterScreens />) or App.tsx (adds <NavigationScreens /> with React Navigation); suggests the cloud |
| iOS (Xcode), Android (Gradle) | prints the Swift package or Gradle line and a quick start; always the cloud, no config file |
With --json, app.entry.status says whether the React Native entry was wrapped (added, already, or manual with a nextStep). doctor checks the package, the storage module and the provider in a React Native app, and the SDK in the build files of a native one. See Apps.
The public key ends up in storage.key from the first of: --key, the saved credentials of login, SPLITHUB_API_KEY in the environment (init asks the cloud which project it belongs to), or the first push. init prints where the key came from. --no-mcp skips the MCP entries.
Where results come from
storage.mode |
status / results read from |
Auth |
|---|---|---|
self |
your app's handler on the local dev server (localhost:3000 for Next.js, 5173 for Vite) or --url / SPLITHUB_URL; raw numbers only |
--token / SPLITHUB_ADMIN_TOKEN when the handler has adminToken |
cloud |
https://go.splithub.io/v1 (or --api / SPLITHUB_API) |
the key saved by login, SPLITHUB_API_KEY, or --token sk_… |
Credentials
login prints a code and a URL. You approve it in the dashboard (and can create the project right there); the dashboard mints a project API key for that terminal and the CLI stores it per directory in ~/.config/splithub/credentials.json (%APPDATA%\splithub on Windows). Nobody pastes a secret into a chat. CI and headless agents use SPLITHUB_API_KEY=sk_… from the project's Settings → API keys.
pk_… is public: it sends events and reads client config. sk_… reads results and pushes config: keep it out of git and out of the browser. An API key can be limited to some areas (experiments, analytics, events, notes, links, alerts, domains, each read or write); a command it may not run fails with INSUFFICIENT_SCOPE and names the missing scope.
Exit codes and JSON
0 success, 1 error (with --json, the error is printed as { error, code, hint? }). status --json returns { project, experiments: [{ experiment, kind, status, goal, name, exposures, visitors, conversions, days, verdict, headline, best, sample, stars, voice, daysToVerdict, display }] }, plus properties in the cloud; results --json returns the full results object described in Statistics.