# Goals and revenue

A goal is the metric an experiment is judged on. Each experiment names one goal; the verdict is computed on it. Other declared goals can still be tracked, but the experiment verdict uses its selected goal.

## Kinds

```ts
goals: {
  signup:       { type: 'event' },
  purchase:     { type: 'event', revenue: true },
  pricing_view: { type: 'pageview', path: '/pricing' },
  docs_any:     { type: 'pageview', path: '/docs/*' },
  cta_click:    { type: 'click', selector: 'a[href="/start"]' },
}
```

| Type | Fires when |
|---|---|
| `event` | your code calls `track('signup')` (React `useTrack`, `client.track`, `window.splithub.track`) |
| `pageview` | a page with that path is viewed; `*` suffix matches a prefix |
| `click` | an element matching the selector is clicked; bound automatically by the SDK and the script |

Goals can also be created in the dashboard (Settings → Goals, or "+ New goal" when creating an experiment) and are delivered to cloud clients. Add them to the local config yourself: `pull` does not write goal definitions. A goal in use cannot be deleted.

## Revenue

```tsx
track('purchase', { revenue: 49 });
```

With `revenue: true` on the goal, results include revenue per variant in `verdict.sample.variants`, available through the API or `results --json`. Revenue per visitor is revenue divided by exposures. The verdict stays on the conversion rate; revenue is a guardrail (a variant that converts more but earns less is not a win).

## Attribution

A visitor converts for a variant when they sent the goal **at or after their first exposure** to that variant. Units are unique visitors: ten purchases by one visitor are one conversion (and the summed revenue). A goal sent before any exposure counts for nothing, which is the right behavior for "did the change cause it".

Only declared goals are tracked. In cloud mode the project must have a running experiment and available quota for ingest to accept events. Earlier goals are not retroactively attributed to a later exposure.

## Props

`track(goal, props)` accepts a small JSON object (up to 4,096 characters when JSON-serialized). `revenue` is the only key with built-in meaning; the rest is stored with the event and shown in the raw-events view and the CSV export.

## Pageviews in SPAs

Client-side navigation does not reload the page, so call `usePageview(pathname)` (React) in the layout, or `client.pageview(path)` with `@splithub/core`. In Next.js, pass `usePathname()` to `usePageview` in a client component; the provider only tracks the initial pageview.

## Good goals

- One primary goal per experiment, decided before the test starts.
- As close to the change as makes sense: a headline test on the home page is judged on "clicked the primary button" or "signed up", not on "paid 30 days later".
- Counts of unique visitors, not of hits. Repeated clicks by one visitor do not make a variant better.
