# The analytics.yaml manifest

One file in your repository declares every event the product may send. It is
reviewed like any other code, and everything else is derived from it.

## The file

```yaml
version: 1
project: liinks

events:
  page_published:
    description: User published their page
    tags: [activation]
    props:
      template:
        type: string
        enum: [minimal, grid, stacked]
      link_count:
        type: number

  subscription_started:
    revenue: true
    tags: [conversion]
```

## Fields

- `version` — the manifest schema version. Currently `1`.
- `project` — the project's domain, which events are written under. Matches
  `data-site` in the tracker snippet and `site` in the API, exactly as
  the dashboard shows it.
- `events` — a map of event name to declaration. Names starting with `$`
  are reserved.
- `description` — what the event means, for the people reviewing the diff and
  for the generated SDK's doc comments.
- `tags` — free-form labels; `activation` and `conversion` are the
  conventional ones.
- `props` — a map of property name to `{ type, enum? }`. Types are
  `string`, `number` and `boolean`.
- `revenue: true` — marks an event as carrying money. Revenue events can only
  be sent from the [server SDK](/docs/sdk); the browser path ignores the field.

Never put personal data in props: no emails, names, addresses or user-authored
text. Props are for the shape of an action, not for who took it.

## What is generated

```sh
npx analytiics codegen    # writes analytics.gen.ts
npx analytiics validate   # checks the manifest, with errors addressed to a human
```

The generated module gives you one typed function per event:

```ts
import { track } from "@/analytics.gen";

track.pagePublished({ template: "grid", link_count: 12 });

// Type error: "carousel" is not assignable to "minimal" | "grid" | "stacked"
track.pagePublished({ template: "carousel" });

// Type error: unknown event. Add it to the manifest first — that is the point.
track.pageShared({});
```

So a renamed event breaks the build rather than the dashboard, and a typo in an
enum value never reaches production.

## What your dashboard does with it

Push the manifest — `analytiics codegen` does it, or `analytiics push` on
its own — and the sections appear. Nothing is configured, and nothing can be:
the rules are fixed, and the way to change what you see is to change the file.

- **Every event gets a card**, with its description, its count over the window
  and its trend. An event you have declared but never sent reads zero rather
  than going missing, because an empty chart is a true statement about your
  code and an absent one looks like an unpopular feature.
- **`enum` props become ranked breakdowns** under the event they belong to.
  A number cannot be an enum, so `link_count` gets no card — it was never
  going to be a list of values.
- **`tags` become sections.** An event carrying several is placed under the
  first and shows the rest, so one chart is never drawn twice on one page.
- **`dashboards.funnels` become funnels**, counting people rather than
  events and following them across the browser/server boundary: a funnel
  ending in a server-side purchase still finds the visit that started it.
- **Events you send but never declared** are listed on their own, with counts.
  That gap is the whole reason a tracking plan exists, so the dashboard names
  it rather than hiding it.

The sections are **off for anyone but you** until you publish them, unlike the
web analytics cards. Your event names describe the inside of your product, and
the count of `subscription_started` is your revenue figure with the currency
taken off. Turn them on per project in settings.

## Keeping it honest in CI

A file is only the source of truth if something notices when the code stops
agreeing with it. That is `analytiics check`: it reads the manifest, finds
every call site in the repository, and reports where the two have drifted —
an event sent but never declared, an event declared but never sent, a property
that has quietly changed shape, a generated module left behind by a manifest
that moved on.

It warns and exits zero unless you pass `--strict`, so adopting it can never
be the reason a release is blocked. See
[the CLI reference](/docs/cli) for the command and a
pull-request workflow to copy.
