> ## 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.

# Custom scripts

> Run your own JavaScript on every page and control which origins your site may load code from.

Run your own JavaScript on every page of your site and control which origins your pages may load code from. To send visits to Google Analytics 4, PostHog, Plausible or Umami, use an [analytics integration](/docs/sites/integrations/analytics/overview) instead, which needs no script.

## 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. They're the JavaScript counterpart to [custom CSS](/docs/sites/customize/design#your-own-css), 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 after the browser parses the HTML.

* These files are plain browser JavaScript rather than modules, so `import` and `export` don't work in them, and you load a library from another host with `document.createElement("script")` and add that host to [`allowedOrigins`](#content-security-policy).
* The build checks the syntax without running your scripts, and a syntax error fails the build with the file name and line number.
* 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

Notra builds a `Content-Security-Policy` header for every page from your repository, and it decides where scripts can load from and which servers they can connect to.

* **Scripts** can come from your site, the analytics integrations you turned on and your `allowedOrigins`. Notra hashes every inline script that was on the page at build time into the policy, so the browser blocks any inline script injected later.
* **Connections** from `fetch`, beacons and WebSockets may go to your site, your integrations and your `allowedOrigins`.
* The policy doesn't limit images, styles, fonts, videos and embeds, 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, set `contentSecurityPolicy` to `false`:

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

If a script doesn't run, open your browser's developer console, where each request the policy blocked shows the origin you need to add to `allowedOrigins`.


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