# Goals (/goals)



A goal is a definition, not a counter. You describe what a win looks like, and Peeko counts it over
whatever range you are looking at, including the months before you wrote it down. Nothing has to be
switched on in advance.

Goals live on the **Goals** tab of a site, and the first four appear as tiles on the dashboard.

## Add a goal [#add-a-goal]

The **Add a goal** card asks for three things.

**Name.** Whatever you will recognize it by, up to 80 characters. "Signups", "Demo booked".

**Counts.** One of three kinds.

| Kind     | Matches                                                          | Example                 |
| -------- | ---------------------------------------------------------------- | ----------------------- |
| Page     | A visit to a path. End it with `*` to count everything under it. | `/thank-you`, `/docs/*` |
| Outbound | Clicks on links to a domain.                                     | `checkout.stripe.com`   |
| Event    | A click Peeko already records, or one you named yourself.        | `signup`, `phone`       |

**The match.** The path, the domain or the event name. A path gets its leading slash added for you,
and a domain is reduced to its host, so pasting a whole URL works.

**Preview** counts the rule against your real traffic before you save it, which is the fastest way to
find out that your thank you page is actually `/thanks`.

A site can hold twenty goals. Archive one to make room: archiving hides a goal everywhere without
losing anything, and restoring it puts its numbers back on every screen for every range. An archived
goal can then be deleted for good, which removes the definition and nothing else, since what it
counted are visits and clicks that stay on the site. A goal saved again with the same rule reports
the same history.

**A goal that is a step in a funnel cannot be archived or deleted.** Peeko names the funnel and asks
you to remove the step there first, because archiving it underneath would change what that funnel
counts without anybody saying so.

### Editing changes history, and that is the point [#editing-changes-history-and-that-is-the-point]

A goal stores no counts, so editing one restates every period, including ones before the edit. If a
goal is a step in a funnel, changing what it counts changes what that funnel counts too, and the form
says so with the funnel's name.

## What Peeko can count without any work [#what-peeko-can-count-without-any-work]

Five kinds of click are recorded for every site with the snippet on it, as long as click tracking is
on in the site's settings. Each is a ready made event name.

| Event name | Fires on                                         |
| ---------- | ------------------------------------------------ |
| `outbound` | A click on a link to another site.               |
| `phone`    | A click on a `tel:` link.                        |
| `email`    | A click on a `mailto:` link.                     |
| `whatsapp` | A click on a `wa.me` or `api.whatsapp.com` link. |
| `form`     | A form being submitted.                          |

The dashboard's Goals card offers these as one click chips while a site has fewer than four goals,
so "count phone clicks" takes no typing at all. Each chip disappears once its goal exists, and they
are absent when click tracking is off.

Peeko stores the destination of an outbound click, and a form's id or path, and never a field value
from a form.

## Counting anything else [#counting-anything-else]

Add `data-peeko-event` to the element you care about. No JavaScript, no build step.

```html
<button data-peeko-event="signup" data-peeko-detail="pricing-page">Create account</button>
```

Or call it yourself, which is what you want after an asynchronous success rather than on the click.

```js
window.peeko?.event?.("signup", "pricing-page");
```

If your code runs before the tracker has loaded, push to the queue instead and it is drained on load.

```js
(window.peekoQ ||= []).push({ name: "signup", detail: "pricing-page" });
```

An event name is up to 64 characters and the optional detail up to 255. There is no properties object:
one name and one string is the whole payload, on purpose, because anything richer becomes a place
where personal data accumulates without anybody deciding to put it there.

Any event name you send shows up in the **Clicks** tab of the Pages card. A goal counts it once the
goal's match spells it identically.

## How a goal is counted [#how-a-goal-is-counted]

Two numbers sit behind every goal, and they answer different questions.

* **Count** is how many times it happened. Four clicks by one person is four.
* **People** is how many distinct people did it at least once.

**Conversion is people over people**: the share of the people in this range who did it at least once.
It uses the stable identity that journeys and funnels use, not the daily one the People tile counts,
so the two populations are not the same number and conversion is not "count divided by the People
tile".

A goal also compares against the previous equal window, under the same baseline rule the rest of the
dashboard uses: no comparison for `All time`, or when the site recorded nothing before the previous
window began, or when that window held fewer than ten people.

## Where goals appear [#where-goals-appear]

The **Goals** tab lists every goal with its count and conversion, and holds the add and edit forms and
the archive. The dashboard shows the first four as tiles with their deltas.

Goals are never shown on a [share link](/share-links). They are your internal definition of winning,
and a shared page is aggregate numbers only.

Over [MCP](/mcp), `list_goals` returns every live goal with its numbers and `get_goal` returns one,
archived goals included, by id. On [the command line](/cli), `peeko goals` prints the same table.
