# Install

> Install Kymo. The script tag records human visitors, the server-side snippet records AI crawlers, and most sites install both. Cookieless, no banner.

Canonical: https://kymo.in/docs/start/install
Last updated: 2026-10-03

Kymo has two install steps, and they capture different audiences. The script tag is cookieless and needs no consent banner.

> **Note:** **The script tag records human visitors.** It runs in a browser.
>
> **The server-side snippet records human visitors and AI crawlers.** It runs on your server, before any HTML is sent.
>
> **Most sites install both.** A crawler requests your page, reads the HTML and leaves without executing anything, so a JavaScript tag cannot see it. If you install only the script tag, your Crawlers page stays empty, and the AI Traffic page shows the people AI sent you but none of the reads.

## Step 1: the script tag, for human visitors

This is the browser beacon. It records pageviews, referrers, outbound clicks and goals from real people. It does not record crawlers.

### 1. Copy the tag

Paste this into your site's HTML, with `data-site` set to your domain.

```html
<script defer src="https://kymo.in/beat.js" data-site="yoursite.com"></script>
```

`data-site` is the value Kymo files events under. It must match the domain registered in your dashboard exactly — `yoursite.com`, not `https://yoursite.com` and not `www.yoursite.com`.

### 1. Put it in the <head>

Place the tag inside `<head>`, or immediately before the closing `</body>`. Either works. `defer` means it never blocks rendering, so the `<head>` is fine.

Add it to every page you want tracked. If your site is a template or single layout file, one edit covers the whole site.

### 2. Optionally declare key-page goals

To count views of specific paths as conversions, list them in `data-goals`, comma-separated:

```html
<script defer src="https://kymo.in/beat.js"
        data-site="yoursite.com"
        data-goals="/signup,/onboarding/complete"></script>
```

WhatsApp, `tel:`, `mailto:`, and form submits are tracked as goals automatically — you do not need to list those.

### 1. Optionally switch first-touch attribution off

First-touch attribution is **on by default**. On the first visit, `beat.js` writes one `localStorage` key, `kymo_ft`, on your own domain. It holds the UTM parameters, the referrer and the landing path of that first visit, plus a timestamp — no cookie, no identifier, nothing that identifies a person. It expires after 13 months. Every later beacon carries it, so a purchase months later can be credited to the campaign that first brought the visitor in.

To switch it off:

```html
<script defer src="https://kymo.in/beat.js"
        data-site="yoursite.com"
        data-attribution="off"></script>
```

Off means `beat.js` reads no storage and writes none. Any other value is on. See [First-touch attribution](https://kymo.in/docs/guides/attribution).

### 1. Deploy

Ship the change. The next real pageview sends a beacon. There is nothing to build and no package to install.

## Step 2: the server-side snippet, for AI crawlers

Kymo detects AI crawlers server-side, from the HTTP request itself. Your server sends the request's user agent to Kymo's `/api/crawl` endpoint, which matches it against the crawler registry and records the visit. The crawler never runs a script, so JavaScript execution is not required. GPTBot, ClaudeBot, PerplexityBot, OAI-SearchBot, Applebot and the rest are all detected this way.

This requires the server-side snippet, not the JavaScript tag. The `beat.js` script tag runs in a browser and measures human visitors. A crawler requests your page, reads the HTML and leaves without executing anything, so a JavaScript tag cannot see it. Install the server-side snippet for Next.js, Express, Astro, SvelteKit, Cloudflare Workers or plain fetch to record crawlers. Most sites install both.

The shape is the same on every backend. Match the request's user agent against a cheap regex, and forward the ones that look like a bot:

```js
// Swap in your framework's real request/response objects for the four below.
const BOT = /bot|crawl|spider|slurp|GPTBot|ChatGPT|Claude|Perplexity|Bytespider/i;

if (BOT.test(userAgent)) {
  fetch('https://kymo.in/api/crawl', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    signal: AbortSignal.timeout(3000),
    body: JSON.stringify({
      site: 'yoursite.com', path: requestPath, ua: userAgent, status: responseStatus
    })
  }).catch(() => {});
}
```

The call is fire-and-forget. Never `await` it before you send your response: send the page first, then report. Each ready-made snippet does that, and gives up after 3 seconds, so a slow or unreachable Kymo can never delay or hang your page. `/api/crawl` answers at once and never returns an error to you: a user agent it does not recognise is discarded silently. Classification happens on Kymo's side, so a new crawler is recognised without any change to this snippet.

> **Note:** Settings carries the ready-made version for Next.js, Express, Astro, SvelteKit, Cloudflare Workers and plain fetch, with your own domain already filled in, plus the optional sampling branch that catches AI agents sending an ordinary browser user agent. Copy the one for your stack from **Settings → AI crawler tracking** rather than adapting the sketch above.

See [The bot taxonomy](https://kymo.in/docs/concepts/bots) for which crawlers are detected and how each is classified.

## What happens on first load

When the page loads, `beat.js` runs once and sends a single pageview beacon to `https://kymo.in/api/beat`. The request is a `text/plain` POST via `navigator.sendBeacon`, so it never blocks the page and never triggers a CORS preflight.

The beacon carries the path, the referrer hostname, any UTM parameters, and the viewport width. It carries no cookie and no identifier you set. Kymo derives a visitor hash server-side from data it already has — see [Cookieless identity](https://kymo.in/docs/concepts/cookieless).

After the first pageview, the script also listens for outbound-link clicks, `history.pushState` navigations (for single-page apps), and form submits.

> **Note:** On `localhost`, `127.*`, and `192.168.*`, `beat.js` deliberately does nothing. You will not see events from local development — deploy to a real domain to test. See [Verify](https://kymo.in/docs/start/verify).

## Excluding your own visits

To keep your own browsing out of your stats, visit your site once with `?kymo=ignore` appended to any URL:

```
https://yoursite.com/?kymo=ignore
```

This sets a `kymo_ignore` flag in your browser's `localStorage` and stops that browser from sending events. Undo it with `?kymo=track`. This is per-browser and affects only you. To exclude an office IP for everyone, use the per-site IP exclusion in Settings instead.

## Next

Confirm it works: [Verify events are arriving](https://kymo.in/docs/start/verify).
