# Install the tracker

> Add the ClickClacks script tag to your site: what each attribute does, and where the tag goes in HTML, Next.js, Nuxt, React, Vue, WordPress, Webflow and GTM.

- Canonical URL: https://clickclacks.io/docs/install
- Section: Tracking
- Last updated: 2026-10-17

ClickClacks is one script tag. Put it in the head of every page and pageviews, clicks and scroll depth start arriving without any other code.

When you add a website or web app source, the app gives you a script tag with that source’s browser key and allowed domains already filled in. Copy that one. The examples on this page use made-up values so you can see the shape.

## The script tag {#the-script-tag}

```html
<script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>
```

It loads `c.js` from `app.clickclacks.io` with `async`, so it never blocks your page from rendering. There’s no npm package for the browser and no Tag Manager template yet: the script tag is the whole install, in every framework. To send events from your servers as well, see [Server-side](https://clickclacks.io/docs/server-side.md). For an iOS or Android app, add a mobile app source instead and see [Mobile apps](https://clickclacks.io/docs/mobile.md).

## Attributes {#attributes}

| Attribute | What it does |
| --- | --- |
| `data-key` | The source’s browser key (`pk_live_…`). It tells ClickClacks which source, and so which project, an event belongs to. Without it the tracker does nothing. |
| `data-domains` | A comma-separated list of hostnames the tracker may run on. On any other hostname it doesn’t start. The app builds it from your source’s domain and, while identity carry-over is on, the project’s other web sources and linked domains. |
| `data-query` (optional) | `off` sends page paths without their query string. Leave it out to keep query parameters, minus [the sensitive ones](https://clickclacks.io/docs/privacy.md#urls). Campaign properties such as `utm_source` are read from the query string, so `off` drops those too. |
| `data-consent` | `opt-out` starts tracking on the first page. `opt-in` keeps it off until your consent tool calls `optIn`. The app’s install step has a switch for it. See [Consent and opt-out](https://clickclacks.io/docs/consent.md). |
| `data-respect-gpc` | `on` turns tracking off for browsers that send [Global Privacy Control](https://clickclacks.io/docs/consent.md#gpc). Leave it out to ignore the signal. |
| `data-click-text` (optional) | `off` stops the text of clicked links and buttons being recorded. |
| `data-carry-identity` (optional) | `off` stops links between your listed domains carrying the visitor’s ID, so each domain sees a separate visitor. |
| `data-mask` (optional) | `strict` masks every character of text and leaves out images in [heatmap page captures](https://clickclacks.io/docs/heatmap-capture.md). |
| `data-capture` (optional) | `off` stops this site from taking [heatmap page captures](https://clickclacks.io/docs/heatmap-capture.md). Events are still sent. |

### The key is public; the domain list is the lock {#key-is-public}

The browser key sits in your page source for anyone to read, so it isn’t what protects your data. The allowed domains are, in two places:

- In the browser, the tracker only starts when the page’s hostname exactly matches an entry in `data-domains`.
- On the server, ClickClacks checks each request’s `Origin` against the domains saved for the source and refuses anything else. A copy of your snippet on another site can’t add events to your project, even with the attribute edited.

Matching is exact: `acme.com` and `www.acme.com` are separate entries. [Domains and identity](https://clickclacks.io/docs/domains.md) covers listing them and following one person across them.

## Where it goes {#frameworks}

Put the tag in the shared document head so it loads on every page, and keep `data-key` and `data-domains` exactly as the app gave them to you. The tracker records route changes in single-page apps by itself (see [Single-page apps](https://clickclacks.io/docs/single-page-apps.md)), so one load per visit is enough.

### Plain HTML {#html}

Add it inside the shared `<head>`, before `</head>`, on every page.

```html title="index.html"
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Acme</title>
    <script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>
  </head>
  <body>
    …
  </body>
</html>
```

### Next.js {#nextjs}

Use `next/script` in the root layout. `afterInteractive` loads it once, after hydration, and it stays loaded as you navigate. Data attributes are passed through to the tag.

**App Router**

```tsx title="app/layout.tsx"
// app/layout.tsx
import Script from 'next/script'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Script
          src="https://app.clickclacks.io/c.js"
          data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d"
          data-domains="acme.com,www.acme.com,app.acme.com"
          strategy="afterInteractive"
        />
      </body>
    </html>
  )
}
```

**Pages Router**

```tsx title="pages/_app.tsx"
// pages/_app.tsx
import type { AppProps } from 'next/app'
import Script from 'next/script'

export default function App({ Component, pageProps }: AppProps) {
  return (
    <>
      <Script
        src="https://app.clickclacks.io/c.js"
        data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d"
        data-domains="acme.com,www.acme.com,app.acme.com"
        strategy="afterInteractive"
      />
      <Component {...pageProps} />
    </>
  )
}
```

### Nuxt {#nuxt}

Add it with `app.head.script` in `nuxt.config`, as below, or with `useHead` in `app.vue` or your root layout.

```ts title="nuxt.config.ts"
// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          src: 'https://app.clickclacks.io/c.js',
          async: true,
          'data-key': 'pk_live_3f9a1c7e5b2d4f6a8c0e1b3d',
          'data-domains': 'acme.com,www.acme.com,app.acme.com',
        },
      ],
    },
  },
})
```

### React, Vue and Svelte with Vite {#react}

A single-page app built with Vite (or Create React App, or the Vue CLI) has one `index.html`. Put the tag in its head; don’t add it from a component, which could load it twice.

```html title="index.html"
<!-- index.html, at the root of a Vite project (React, Vue, Svelte, Preact, Solid…) -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>
    <title>Acme</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

### SvelteKit {#sveltekit}

Add it to `src/app.html`, above `%sveltekit.head%`.

```html title="src/app.html"
<!-- src/app.html -->
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>
    %sveltekit.head%
  </head>
  <body data-sveltekit-preload-data="hover">
    <div style="display: contents">%sveltekit.body%</div>
  </body>
</html>
```

### WordPress {#wordpress}

Print the tag in the head with the `wp_head` action, from a child theme’s `functions.php` or a code-snippets plugin. Any “insert header code” plugin works the same way: paste the tag into its header box.

```php title="functions.php"
<?php
// In your child theme's functions.php, or a code-snippets plugin.
add_action('wp_head', function () {
    echo '<script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>';
});
```

### Webflow {#webflow}

1. Open **Site settings › Custom code**.
2. Paste the tag into **Head code** and save.
3. Publish the site. Custom code only runs on the published site.

If you want the `.webflow.io` staging domain tracked too, add it to the source’s domains; otherwise it stays out of your data.

### Shopify {#shopify}

1. In the Shopify admin, open **Online Store › Themes**, then **Edit code** on your live theme.
2. Open `layout/theme.liquid` and paste the tag just before `</head>`.
3. Save.

```liquid title="theme.liquid"
{% comment %} layout/theme.liquid, just before </head> {% endcomment %}
<script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>
</head>
```

This covers your storefront. Shopify’s checkout doesn’t run theme code, so checkout pages aren’t tracked this way; send orders from your backend with the [server API](https://clickclacks.io/docs/api.md) instead. List both your own domain and any `.myshopify.com` address visitors reach.

### Google Tag Manager {#gtm}

There’s no ClickClacks template in the Tag Manager gallery; a Custom HTML tag does the job.

1. Create a tag of type **Custom HTML** and paste the script tag into it, attributes and all.
2. Trigger it on **All Pages** (or **Initialization – All Pages** to load it earlier).
3. If you manage consent in Tag Manager, gate the tag on it. See [Consent and opt-out](https://clickclacks.io/docs/consent.md).
4. Submit and publish the container.

## Content Security Policy {#csp}

If your site sends a `Content-Security-Policy` header, allow the tracker to load and to send events:

```
Content-Security-Policy:
  script-src 'self' https://app.clickclacks.io;
  connect-src 'self' https://app.clickclacks.io
```

- `script-src` for `c.js`, and for the small screenshot helper it loads from the same origin when a heatmap capture is due.
- `connect-src` for events and heatmap captures.
- With a [custom domain](https://clickclacks.io/docs/custom-domain.md), add it to both. Keep `app.clickclacks.io` in `connect-src`: heatmap captures are always sent there.
- If you use nonces, put the nonce on the script tag. The tracker passes it on to the helper it loads.

## Hand it to your coding agent {#coding-agent}

Next to the snippet on the install step, **Copy agent prompt** copies a prompt with your exact script tag, your allowed domains, the placement rules, and two guard rails: don’t add a package or backend SDK, and don’t invent events during installation. With the example values it reads:

```title="agent prompt"
Install the ClickClacks browser tracker in this codebase.

Use this exact script tag (the browser key and allowed domains are specific to this source):

<script async src="https://app.clickclacks.io/c.js" data-key="pk_live_3f9a1c7e5b2d4f6a8c0e1b3d" data-domains="acme.com,www.acme.com,app.acme.com" data-consent="opt-out" data-respect-gpc="on"></script>

Allowed domains: acme.com,www.acme.com,app.acme.com

Put it in the document head so it loads on every page:
- Plain HTML: add it inside the shared <head> element before </head>.
- Nuxt: add the script with useHead in app.vue or the root layout, or with the app.head.script setting in nuxt.config. Preserve the data-key and data-domains attributes exactly.
- Next.js: add it to app/layout.tsx (or app/head.tsx), pages/_document.tsx, or the project's equivalent shared document head. Load it afterInteractive or as a normal async script.

Do not add a package or backend SDK. This script captures browser pageviews, clicks and scroll depth automatically. Existing calls to window.clickclacks('event', name, properties) and window.clickclacks('identify', id, properties) may use the same tracker, but do not invent new events as part of installation.

Verify the installation by loading or opening a page on one of the allowed domains in a private window, then return to this onboarding screen and check that the first event is received.
```

## Check the first event {#verify}

1. Deploy the change, or open a build that runs on one of your allowed domains.
2. Keep the app’s install step open. It shows **Listening for your first event…** and checks for you.
3. Load a page on your site in another tab.
4. The step switches to **First event received**, with the event, page path, browser and country.

Already past onboarding? Open **Realtime**, which shows every event from the last 30 minutes as it arrives. If nothing shows up, work through [Debugging](https://clickclacks.io/docs/debugging.md).

## Next steps {#next}

- [Autocapture](https://clickclacks.io/docs/autocapture.md): exactly what arrives without any code.
- [Custom events](https://clickclacks.io/docs/events.md): send the moments that matter to your product.
- [Identify people](https://clickclacks.io/docs/identify.md): tie visits to your user IDs.
