Install the tracker

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.

Updated

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

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. For an iOS or Android app, add a mobile app source instead and see Mobile apps.

Attributes

AttributeWhat 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. Campaign properties such as utm_source are read from the query string, so off drops those too.
data-consentopt-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.
data-respect-gpcon turns tracking off for browsers that send Global Privacy Control. 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.
data-capture (optional)off stops this site from taking heatmap page captures. Events are still sent.

The key is public; the domain list is the lock

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 covers listing them and following one person across them.

Where it goes

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), so one load per visit is enough.

Plain HTML

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

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

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/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>
  )
}

Nuxt

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

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

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.

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

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

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

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.

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

  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

  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.
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 instead. List both your own domain and any .myshopify.com address visitors reach.

Google Tag Manager

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.
  4. Submit and publish the container.

Content Security Policy

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

http
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, 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

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:

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

  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.

Next steps