Docs / Experiments View as Markdown

Targeting flows

Country, device, UTM, cookie, time and visitor conditions as a node graph, in the dashboard and in the config.

Who enters an experiment, and with which split, can depend on country, device, browser, UTM parameters, cookies, time, or whether the visitor is new or returning. In SplitHub this is a small node graph, the flow, on the experiment.

In the dashboard

Open an experiment → Targeting. The editor has three node types:

  • Condition: one or more conditions, combined with match: 'all' or 'any'. Fields include country, region, city, device, browser, os, language, referrer, url, path, utm_source, utm_medium, utm_campaign, utm_term, utm_content, query, cookie, visitor, hour, weekday. For query and cookie, set key to the parameter or cookie name. Operators: is, is_not, in, not_in, contains, not_contains, starts_with, matches, exists, not_exists. Values are strings; in uses a comma-separated list.
  • Split: weighted outputs; each output goes to a variant or to another node.
  • Variant / Skip: the leaf. Skip means "not in the experiment": the visitor sees the control and no exposure is recorded.

A toggle switches to a simple list view (sections top to bottom, drag to reorder) for flows that are just "if country in EU → split A, else → split B". While the flow editor is open, variant editing is locked so the two cannot drift; on save the editor collapses to a one-line summary.

In the config

The same graph serializes as flow on the experiment and comes into splithub.config.ts with npx splithub pull:

'hero-title': {
  kind: 'value',
  status: 'running',
  variants: { control: { weight: 1, value: 'A' }, bold: { weight: 1, value: 'B' } },
  flow: {
    start: 'geo',
    nodes: {
      geo:   { type: 'condition', conditions: [{ field: 'country', op: 'in', value: 'DE,AT,CH' }], then: 'eu', else: 'rest' },
      eu:    { type: 'split', outputs: [{ weight: 50, to: 'control' }, { weight: 50, to: 'bold' }] },
      rest:  { type: 'split', outputs: [{ weight: 90, to: 'control' }, { weight: 10, to: 'bold' }] },
      control: { type: 'variant', variant: 'control' },
      bold: { type: 'variant', variant: 'bold' },
    },
  },
}

defineConfig validates the flow: node references, supported fields and operators, split weights, and declared variant names are checked. Design each path to end in a variant or skip; avoid cycles.

Where it runs

Everywhere the assignment runs, with the same answer:

  • the React SDK (browser context: device, browser, path, query, cookies, time; country from the cloud's ctx when in cloud mode);
  • the Next.js middleware (request headers and Cloudflare/Vercel geo);
  • the drop-in script;
  • server code using assign(config, experimentId, visitorId, { context }).

Pass supported context fields with createClient(config, { context: { country: 'DE' } }) or the provider's context prop. For a plan stored in a cookie, use { field: 'cookie', key: 'plan', op: 'is', value: 'pro' }.

When to use it

  • Roll a change out to one market first.
  • Keep a mobile-only variant out of desktop traffic.
  • Test only paid traffic (utm_medium is cpc).
  • Exclude employees by cookie.

Remember the statistics: targeting narrows the sample. The verdict is about the visitors who entered, not about everyone.

SplitHub
Test ideas. Ship winners.