1. Deploy the server

    Print a starter server for Workers, Vercel, Bun or Node, then deploy it like any small app.

    Terminal
    npx analytik init --target worker
  2. Add the snippet to your site

    Paste it into the <head> of every page. It sets no cookies.

    HTML
    <script defer src="https://analytics.example.com/js/analytik.js" data-site="my-site" data-mode="strict"></script>
  3. Open the dashboard

    Embed it in any React app and sign in with your admin password.

    Admin.tsx
    import { Dashboard } from 'analytik/dashboard'
    import 'analytik/dashboard/styles.css'
    
    export const Admin = () => <Dashboard api="https://analytics.example.com/api" />

Script tag
<script defer src="https://analytics.example.com/js/analytik.js" data-site="my-site" data-mode="strict"></script>

Server setup prompt

Let your coding assistant deploy Analytik. Pick where it runs; the prompt uses the host and mode from More options.

Server setup prompt: choose a platform
Prompt for Cloudflare Workers + D14,213 characters
# Set up the Analytik server on Cloudflare Workers + D1

## Goal

Deploy analytik (privacy-first, self-hosted web analytics, npm package `analytik`) as a Cloudflare Worker with a D1 database, serving the tracker as a static asset. It must answer at https://analytics.example.com: the API under `/api` and the tracker script at `/js/analytik.js`. Work in a new, separate project, not inside the website that will be tracked.

## Install

```sh
npm install analytik hono && npm install -D wrangler @cloudflare/workers-types
```

Analytik is ESM only and needs Node 22.19+ or Bun 1.3+ when it runs outside Workers. `hono` is a peer dependency.

## Code

`src/worker.ts`:

```ts
import { createAnalytik, type Analytik } from 'analytik/server'
import { d1Storage } from 'analytik/storage/d1'

interface Env {
  DB: D1Database
  ADMIN_PASSWORD: string
  SESSION_SECRET: string
}

// Build the app once per isolate, not once per request.
let analytik: Analytik | undefined

export default {
  fetch(request: Request, env: Env, ctx: ExecutionContext) {
    analytik ??= createAnalytik({
      storage: d1Storage(env.DB),
      admin: { password: env.ADMIN_PASSWORD, secret: env.SESSION_SECRET },
      privacy: { mode: 'strict' },
    })
    return analytik.app.fetch(request, env, ctx)
  },
}
```

`wrangler.jsonc`:

```jsonc
{
  "name": "analytik",
  "main": "src/worker.ts",
  "compatibility_date": "2026-10-01",
  // Only API calls run the Worker; analytik.js is a free static asset.
  "assets": { "directory": "./public", "run_worker_first": ["/api/*"] },
  "d1_databases": [{ "binding": "DB", "database_name": "analytik", "database_id": "<from wrangler d1 create>" }],
  "routes": [{ "pattern": "analytics.example.com", "custom_domain": true }]
}
```

## Deploy

1. `npx wrangler d1 create analytik` and put the printed `database_id` into `wrangler.jsonc`.
2. `mkdir -p public/js && cp node_modules/analytik/dist/analytik.js public/js/analytik.js` (repeat after every package upgrade).
3. `npx wrangler secret put ADMIN_PASSWORD` and `npx wrangler secret put SESSION_SECRET`: `ADMIN_PASSWORD` (the dashboard password, not empty) and `SESSION_SECRET` (`openssl rand -hex 32`, at least 32 characters). For local runs put both in `.dev.vars`.
4. `npx wrangler deploy`. The schema is created on the first request (autoMigrate).

Client IP and location come from Cloudflare automatically (`cf-connecting-ip`, `request.cf`).

## Privacy mode

The default mode for new sites is `strict` (each site can override it). Strict (default): the tracker stores nothing on the device and the server never stores the IP. The visitor id is a salted hash that changes every month. It is designed so no cookie banner is needed (this is a design goal, not legal advice). `identify()` is ignored.

## Create the first site

```sh
curl -c cookies.txt -X POST https://analytics.example.com/api/auth/login -H 'Content-Type: application/json' -d '{"password":"<ADMIN_PASSWORD>"}'
curl -b cookies.txt -X POST https://analytics.example.com/api/sites -H 'Content-Type: application/json' -d '{"name":"My site","domain":"example.com","currency":"EUR"}'
```

Use the real domain of the tracked website (events from other hosts are rejected). The response contains `site.id` (the value for `data-site`) and `apiKey` (for server-side payments and goals, shown once).

## Verify

1. `curl https://analytics.example.com/api/health` returns `{"ok":true,...}`.
2. `curl -I https://analytics.example.com/js/analytik.js` returns 200 with a JavaScript content type.
3. `npx analytik doctor --url https://analytics.example.com` reports no problems.
4. A test event is accepted with 202: `curl -i -X POST https://analytics.example.com/api/event -H 'Content-Type: text/plain' -H 'Origin: https://example.com' -d '{"w":"<site id>","t":"pageview","u":"https://example.com/"}'` (use the site's domain).

## Rules

- Never commit secrets; read them from the platform environment.
- Do not add cookies, fingerprinting or third-party scripts on top of what Analytik does.
- Keep the API under `/api`; the tracker posts to `/api/event` on the origin of the script by default.
- Stop and tell me if a step needs an account, a paid plan or DNS access you do not have.

Site integration prompt

Let your coding assistant add the tracker to your site, in the right file for your framework.

Site integration prompt: choose a platform
Prompt for Plain HTML3,083 characters
# Add Analytik tracking to your Plain HTML site

## Goal

Add the Analytik tracker (privacy-first, self-hosted web analytics) to this Plain HTML site so every page sends pageviews to https://analytics.example.com. The analytics server is already running; only the website changes. Site id: `my-site`, privacy mode: `strict`. If the site id looks like a placeholder, ask me for the real one.

## The snippet

```html
<script defer src="https://analytics.example.com/js/analytik.js" data-site="my-site" data-mode="strict"></script>
```

## Where to put it in Plain HTML

Paste the tag into the `<head>` of every page, or once into the shared header include if the site has one.

`index.html (every page)`:

```html
<script defer src="https://analytics.example.com/js/analytik.js" data-site="my-site" data-mode="strict"></script>
```

Add it once, site wide. Do not self-host or bundle the script, and do not add a second analytics snippet for the same site.

## Privacy

- Strict mode stores nothing on the device: no cookie, no localStorage, no sessionStorage. The IP is used for a salted hash and never stored.
- By design no cookie banner is needed for Analytik in this mode, so do not put it behind a consent manager and do not list a cookie for it (not legal advice; the site owner decides).
- Do not call `analytik.identify()`: strict mode ignores it. Identify people only with their consent, on a site switched to enriched or persistent mode.

## Goals and revenue

Pick the events that matter for this site (sign-ups, checkouts) and add them:

```html
<!-- Declarative: a click on this element sends the goal "signup" with the prop plan=pro. -->
<button data-analytik-event="signup" data-analytik-plan="pro">Start free trial</button>
```

```js
window.analytik?.track('signup', { plan: 'pro' })
// Revenue in major units; orderId de-duplicates repeats (for example a reloaded thank-you page).
window.analytik?.revenue('purchase', 49, { currency: 'EUR', orderId: 'order_1042' })
```

For payments confirmed on the server (Stripe, Dodo, Lemon Squeezy or your own backend), prefer `POST https://analytics.example.com/api/v1/payments` with the site API key, so revenue is recorded even when the visitor closes the tab. To call `analytik` before the script has loaded, add the queue stub first: `window.analytik = window.analytik || function () { (window.analytik.q = window.analytik.q || []).push(arguments) }`.

## Verify

1. Open the site in a normal browser window (the tracker ignores automated browsers) with ad blockers off. The first pageview is sent after the first interaction or 3 seconds of visibility.
2. In DevTools > Network, filter for `event`: a `POST https://analytics.example.com/api/event` request returns **202**. 403 means the page's host is not the site's domain (on localhost add `data-allow-localhost` and enable allowLocalhost for the site); 404 means the site id is wrong.
3. Open the Analytik dashboard: the visit shows up in Realtime within a few seconds.
4. If you added goals or revenue, trigger one and check that it appears under Events or Revenue.