> ## Documentation Index
> Fetch the complete documentation index at: https://www.usenotra.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Analytics and scripts

> Add an analytics provider, run your own JavaScript and control which origins your site may load from.

Every site comes with built-in analytics, and this page covers how to send visits to your own analytics tool as well, run your own JavaScript and control which origins your pages may load code from.

## Built-in analytics

Every site counts its own visitors and their time on page with nothing to install, and you can turn this off under **Settings → Danger zone**. Open the site's **Analytics** page for people and AI agents on the site, or **Traffic** in GEO for AI crawlers and referrals across all your domains, while previews never count. See [AI agents and search](/docs/sites/agents#ai-traffic-analytics).

## Analytics integrations

To also send visits to your own analytics tool, add it on the site's **Integrations** page or under `integrations` in `blog.json`. The Integrations page saves a draft of `blog.json` that goes live when you publish it from the editor. Notra adds the provider's script tag to the `<head>` of every page and allows its hosts in the [Content-Security-Policy](#content-security-policy).

```json blog.json theme={"system"}
{
  "name": "Acme",
  "integrations": {
    "databuddy": { "clientId": "your-client-id" },
    "plausible": { "domain": "acme.com" }
  }
}
```

You can turn on several providers at once, and Notra checks every ID against the provider's format. If an ID is wrong or a key is unknown, the build fails with an error that points to the setting.

| Integration | Settings | Notes |
| - | - | - |
| `databuddy` | `clientId` | The client ID from Databuddy |
| `plausible` | `domain` | The site's domain as you added it in Plausible |
| `posthog` | `apiKey`, `apiHost` | `apiKey` is the project API key (`phc_…`), and `apiHost` defaults to `https://us.i.posthog.com`, so set it to `https://eu.i.posthog.com` for EU Cloud or to your reverse proxy |
| `ga4` | `measurementId` | Google Analytics 4, for example `G-ABC123XYZ9` |

Another tool, such as a chat widget or a tag manager, goes in a [custom script](#custom-scripts).

<Note>
  Previews use the same integrations as your live site, so filter by hostname in your analytics tool to keep previews out of your numbers. Preview hosts look like `pr-7--acme.notra.site`.
</Note>

## Custom scripts

Every `.js` file outside the content folders runs on every page, whether it's `script.js`, a file in a `scripts/` folder or a file like `analytics.js` at the root. These files work like [custom CSS](/docs/sites/customize/design#your-own-css) for JavaScript, while a `.js` file in `snippets/`, `blog/` or `changelog/` is a component you import instead.

```text theme={"system"}
script.js               # runs first
analytics.js            # then every other script, sorted by path
scripts/chat-widget.js
scripts/tracking/hotjar.js
```

Notra loads every script on every page with `defer` from your site's own address, and the scripts run in the order shown above once the HTML has been parsed.

* **Plain browser JavaScript**: these files are not modules, so `import` and `export` don't work in them, and `document.createElement("script")` loads a library from another host.
* **Syntax check only**: the build checks the syntax and never runs your scripts, and a syntax error fails the build and shows the file and line.
* **No imports from MDX**: your MDX can't import these files, so for a component, use a [snippet](/docs/sites/custom-components) in `snippets/` instead.

```js scripts/external-links.js theme={"system"}
// Open links to other sites in a new tab.
for (const link of document.querySelectorAll("main a[href^='http']")) {
  if (link.host !== location.host) {
    link.target = "_blank";
    link.rel = "noopener";
  }
}
```

## Content-Security-Policy

Every page comes with a `Content-Security-Policy` header that says where scripts may come from and which servers they may connect to, and Notra builds it from your repository.

* **Scripts**: they can come from your site, the analytics integrations you turned on and your `allowedOrigins`. Notra allows inline scripts only if they were on the page at build time and hashes each of them, so a script injected later doesn't run.
* **Connections**: `fetch`, beacons and WebSockets may connect to your site, your integrations and your `allowedOrigins`.
* **Images, styles, fonts, videos and embeds**: the policy doesn't limit them, so posts can still show images and videos from anywhere.

If your custom scripts or components load code from another host or call an API, add that origin:

```json blog.json theme={"system"}
{
  "security": {
    "allowedOrigins": [
      "https://cdn.example-widget.com",
      "https://*.example-widget.com",
      "wss://*.example-widget.com"
    ]
  }
}
```

Origins must start with `https://` or `wss://` and have no path, and `https://*.example.com` covers every subdomain. A `wss://` origin only allows connections rather than scripts, and you can list up to 32 origins.

To turn the policy off entirely:

```json blog.json theme={"system"}
{
  "security": { "contentSecurityPolicy": false }
}
```

If a script doesn't run, open your browser's developer console, which lists any request the policy blocked with the origin you need to add.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.