# Installation

Clicktag is one async `latest.js` tag, about 4 KB compressed, with no npm package, for plain HTML, React with Vite or the Next.js App Router. It tracks single-page app navigation through `pushState` and `popstate` and takes its settings from `data-` attributes or `window.tt_settings`.

## Script placement

Load `latest.js` once in your root document. Do not mount duplicate script tags across child views. For a new site, copy the tag from Site settings > Installation, step 1: keep both `data-hostname` and `data-verify`. In the examples below, replace `YOUR_SITE_VERIFY_TOKEN` with that site’s `verify_token`, not your account access token.

### Plain HTML

Place the script in your shared template header or footer:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My App</title>
    <script
      async
      src="https://clicktag.io/latest.js"
      data-hostname="example.com"
      data-verify="YOUR_SITE_VERIFY_TOKEN"
    ></script>
  </head>
  <body>
    <main></main>
  </body>
</html>
```

### React and Vite

Add the script directly inside `index.html` at the project root:

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
    <script
      async
      src="https://clicktag.io/latest.js"
      data-hostname="example.com"
      data-verify="YOUR_SITE_VERIFY_TOKEN"
    ></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>
```

### Next.js (App Router)

Place a raw `<script>` tag inside `app/layout.tsx` within the `<body>` element. Clicktag does not require an external npm package:

```tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        <script
          async
          src="https://clicktag.io/latest.js"
          data-hostname="example.com"
          data-verify="YOUR_SITE_VERIFY_TOKEN"
        />
      </body>
    </html>
  );
}
```

The tag is included in the rendered document; tracking runs in the visitor’s browser. For strict CSP, follow your framework’s nonce or hash setup for any inline code.

For visitors without JavaScript, you can add the optional noscript pixel to the same `<body>`. JSX needs `referrerPolicy` and a style object; the HTML form of the pixel fails the type check in `next build`:

```tsx
<noscript>
  <img
    loading="lazy"
    src="https://clicktag.io/noscript.gif?hostname=example.com"
    alt=""
    referrerPolicy="no-referrer-when-downgrade"
    style={{ position: "fixed", top: 0, left: 0 }}
  />
</noscript>
```

Keep it a plain `<img>`, not `next/image`, even when `@next/next/no-img-element` warns.

## Single-page navigation

`latest.js` automatically hooks the browser `window.history.pushState` API and listens to `popstate` events. These navigation events trigger pageviews when the tracked path changes. Consecutive identical paths are suppressed; `replaceState` alone does not trigger a pageview.

Do not add a second manual pageview for a route already covered by automatic tracking.

If your frontend uses hash-based routing (such as `example.com/#/dashboard`), add the `data-mode="hash"` attribute to track hash fragment transitions:

```html
<script
  async
  src="https://clicktag.io/latest.js"
  data-hostname="example.com"
  data-verify="YOUR_SITE_VERIFY_TOKEN"
  data-mode="hash"
></script>
```

## Manual pageview tracking

If you prefer manual control over pageviews, disable automatic collection with `data-auto-collect="false"`. When disabled, trigger pageviews using `window.tt_pageview(path, metadata)`.

Calls recording an identical consecutive path are automatically suppressed. Verify that the script has loaded before calling `window.tt_pageview`:

```html
<script>
  function trackInitialPage() {
    if (typeof window.tt_pageview !== "function") {
      console.warn(
        "Pageview tracking is unavailable; check DNT and script loading.",
      );
      return;
    }

    window.tt_pageview(window.location.pathname);
  }
</script>
<script
  async
  src="https://clicktag.io/latest.js"
  data-hostname="example.com"
  data-verify="YOUR_SITE_VERIFY_TOKEN"
  data-auto-collect="false"
  onload="trackInitialPage()"
></script>
```

## Script configuration options

Configure behavior by setting data attributes on the `<script>` tag:

- `data-hostname`: The registered site hostname (e.g., `example.com`), including when the website redirects between the apex and `www`. Use the bare hostname as registered. The collector normalizes it the way registration does, so a scheme, path or stray space still matches, but a hostname with a port never does.
- `data-verify`: The site’s public ownership challenge from site settings or `GET /api/sites/{hostname}`. Required for automatic verification of an unverified site; existing verified sites do not need a new tag.
- `data-ignore-pages`: Comma-separated path patterns with wildcards to ignore (e.g., `/admin/*,/checkout/receipt`). Each pattern must match the whole path, case-insensitively; `*` matches any characters.
- `data-allow-params`: Extra comma-separated query keys to keep, beyond the campaign parameters the script already recognizes (e.g., `plan`). Keep values non-sensitive.
- `data-mode`: Set to `"hash"` for hash-based client routers.
- `data-auto-collect`: Set to `"false"` to disable automated pageviews on load and history navigation.
- `data-strict-utm`: Set to `"true"` to keep automatic campaign-parameter collection to `utm_` keys rather than short aliases such as `source`, `campaign` and `ref`.
- `data-retention`: Set to `"false"` (or `retention: false` in `window.tt_settings`) to turn off returning-visitor tracking. When on, the first pageview of each page load requests `/r`. The browser keeps the response fresh in its HTTP cache until the UTC day ends, so the request reaches Clicktag about once a day per browser; its `Last-Modified` date holds the visitor's first and last visit day, for this site only. The script writes no browser storage of its own. So other tabs, hard reloads and private windows don't count twice, the server remembers that date under the day's visitor hash until the UTC day ends.
- `data-namespace`: Defaults to `tt`; `data-namespace="shop"` uses `shop_event`, `shop_metadata`, and manual-mode `shop_pageview`, with the guard `tt_shop_loaded`. Configuration remains in `window.tt_settings`. Give each tracker its own namespace, and avoid `sa`: it gets no `sa_pageview`, and its default event name `sa_event` is reserved.
- `data-event-global`: Overrides only the event function name (`data-sa-global` is a legacy alias). Use an unused global or its preload queue, never `sa_event`: Clicktag reserves that name for Simple Analytics and will not consume its queue or replace its API.
- `data-ignore-metrics`: Comma-separated metrics the script should not send: `referrer`, `utm`, `country`, `session`, `timeonpage`, `scrolled`, `useragent`, `screensize`, `viewportsize` or `language`. Entries match by prefix, so `scrolled` also drops screen size. Ignoring `useragent` only stops the script from sending it; the collector still reads the browser's `User-Agent` request header.
- `data-collect-dnt`: Set to `"true"` (or `collectDnt: true` in `window.tt_settings`; `data-ignore-dnt` and `data-skip-dnt` are aliases) to skip the script's Do Not Track check. See [Privacy and Do Not Track](/docs/installation#privacy-and-do-not-track) for what the collector still drops.
- `data-non-unique-hostnames`: Comma-separated referrer hostnames, without `www.`, whose visits should not be flagged as unique arrivals, such as your other domains or a payment provider. Visitor counts use the daily visitor hash; this flag is only their fallback for rows without one.
- `data-path-overwriter`: The name of a global function that receives `{ path }` and returns the path to record; a falsy return keeps the original. It runs before `data-ignore-pages` matching. If it throws, the script logs the error and keeps the original path.
- `data-metadata-collector`: The name of a global function called before each pageview with `type: "pageview"` and `path`, and before each event with `type: "event"` and `event`, alongside the metadata gathered so far. The object it returns is merged into that hit's metadata. If it throws, the script logs the error and sends the hit without metadata.

You can also assign settings via `window.tt_settings` before the script loads:

```html
<script>
  window.tt_settings = {
    hostname: "example.com",
    autoCollect: false,
  };
</script>
<script
  async
  src="https://clicktag.io/latest.js"
  data-hostname="example.com"
  data-verify="YOUR_SITE_VERIFY_TOKEN"
></script>
```

The default globals are `tt_settings`, `tt_event`, `tt_metadata`, and manual-mode `tt_pageview`; the default loaded guard is `tt_tt_loaded`. An occupied event function without a preload queue is not overwritten; the script warns while pageviews continue. For most settings, `window.tt_settings` overrides the equivalent attribute; `data-auto-collect="false"`, `data-retention="false"` and `data-strict-utm="true"` still take effect. Avoid conflicting configurations and set them before `latest.js` executes. Clicktag does not host the optional [automatic-events companion scripts](/docs/events#automated-event-scripts).

## Privacy and Do Not Track

By default, `navigator.doNotTrack === "1"` stops normal collection after the script loads: no pageviews or events are sent, and the script does not install `tt_event` or `tt_pageview`. Separately, the collector drops pageviews, events and engagement updates whose request carries a `DNT: 1` or `X-Do-Not-Track: 1` header. `data-collect-dnt` only skips the check in the script, which never sends the collector's `collect-dnt` override, so those requests are still dropped. Keep this default to respect the visitor's setting.

## Content Security Policy (CSP)

Clicktag sends pageviews, events and the `/r` returning-visitor request as `new Image()` requests. `/append` engagement updates use `navigator.sendBeacon` when the visitor leaves the page; after a single-page navigation, or when `sendBeacon` is unavailable or refuses the data, they go as an image request to `simple.gif` instead. Tags served from `clicktag.io` or `clicktag.app` send beacons to `clicktag.io`. Existing tags served from `totallytics.com` or `totallytics.app` keep sending to `totallytics.com`.

If your site serves a Content Security Policy header, merge our hosts into your existing directives. Do not overwrite your wider security rules:

```text
script-src 'self' https://clicktag.io;
img-src 'self' https://clicktag.io;
connect-src 'self' https://clicktag.io;
```

Self-hosted copies of `latest.js` downloaded before 25 September 2026 still beacon to `https://analytics.bitgate.dev`; keep that host in `img-src` and `connect-src` as well, or download the current script.

The `script-src` entry must match the host in your snippet. New installations use `https://clicktag.io`; a tag loaded from `https://clicktag.app` needs that host in `script-src` instead. Legacy Totallytics tags still need their existing script host in `script-src` and `https://totallytics.com` in `img-src` and `connect-src`. A self-hosted copy of `latest.js` needs only `'self'`, but automatic verification and the setup check only recognize tags loaded from Clicktag hosts, so verify such a site by [DNS or file](/docs/sites#verify-ownership). If your policy defines `script-src-elem`, allow the tag host there as well. Inline settings, event queues, and `onload` examples also need your existing inline-code policy; prefer external app code or your framework's nonce/hash mechanism rather than adding `unsafe-inline`.

Site settings > Installation, step 3, **Run setup check** fetches your homepage and checks its `Content-Security-Policy` response header against the detected tag host and beacon endpoints. Nonce/hash policies need a browser check. It does not read `<meta>` or report-only policies, and it cannot see tags injected by client-side code; with no tag in the served HTML, it checks the script host `clicktag.io`.

## Testing on localhost

By default, `latest.js` extracts `location.host` when `data-hostname` is omitted. On local development environments, this results in values like `localhost:3000`.

The script has no localhost exclusion and also sends from local development. A local hostname such as `localhost:3000` cannot be registered, so the collector ignores it; an explicit production `data-hostname` records local test traffic against your live site. To verify integrations, test on an explicit staging or production domain that matches a registered hostname. Do not direct localhost test traffic to your production hostname.

Next, explore [Custom Events](/docs/events) or review the [Troubleshooting Guide](/docs/troubleshooting).
