# Install the website feedback widget

Install Annot8 in Next.js, Nuxt, SvelteKit, or WordPress with one script tag. Configure CSP and verify the feedback widget works.

Source: https://docs.annot8.app/docs/installation



Annot8 loads from a single script tag. There is no npm package to install and
nothing to vendor — loading from the hosted URL is what keeps the widget
up to date.

## The snippet [#the-snippet]

Copy the generated snippet from **Project → Settings → Installation**. It
already contains your project key, backend URL, and dashboard origin:

```html
<script
  src="https://www.annot8.app/widget.js"
  data-project-id="pj_xxxxxxxxxxxx"
  data-convex-url="https://your-deployment.convex.cloud"
  data-dashboard-url="https://www.annot8.app"
  async
></script>
```

<Callout type="warn">
  Use the exact values from your dashboard. Copying another workspace's project
  key or backend URL fails the origin check and the widget silently does
  nothing.
</Callout>

## Script attributes [#script-attributes]

| Attribute                | Required | Description                                                                                               |
| ------------------------ | -------- | --------------------------------------------------------------------------------------------------------- |
| `data-project-id`        | Yes      | Your project key (`pj_...`)                                                                               |
| `data-convex-url`        | Yes      | Backend URL for your deployment                                                                           |
| `data-dashboard-url`     | No       | Dashboard origin. Inferred from the script `src` when omitted                                             |
| `data-public-slug`       | No       | Public link slug for guest feedback. Usually auto-discovered — see [Feedback links](/docs/feedback/links) |
| `data-theme`             | No       | `light`, `dark`, or `auto` (default)                                                                      |
| `data-label`             | No       | Launcher label, overriding the dashboard setting                                                          |
| `data-analytics-consent` | No       | `true` only after your consent manager has collected consent                                              |
| `data-config`            | No       | A JSON object carrying any of the above keys                                                              |

Values are validated before use. A malformed project key, a non-HTTPS backend
URL, or an unrecognised theme is discarded rather than passed through.

### Alternatives to `data-convex-url` [#alternatives-to-data-convex-url]

If your CMS or template strips `data-*` attributes, the loader also reads:

```html
<meta name="annot8-convex-url" content="https://your-deployment.convex.cloud" />
```

or a global set before the script runs:

```html
<script>
  window.__ANNOT8_CONVEX_URL__ = "https://your-deployment.convex.cloud";
</script>
```

## Where to put it [#where-to-put-it]

It has to load on **every** route, not inside a single page component.

<Tabs items="[&#x22;Next.js App Router&#x22;, &#x22;Next.js Pages Router&#x22;, &#x22;Nuxt&#x22;, &#x22;SvelteKit&#x22;, &#x22;WordPress&#x22;]">
  <Tab value="Next.js App Router">
    ```tsx title="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://www.annot8.app/widget.js"
              data-project-id={process.env.NEXT_PUBLIC_ANNOT8_PROJECT_ID}
              data-convex-url={process.env.NEXT_PUBLIC_ANNOT8_CONVEX_URL}
              strategy="afterInteractive"
            />
          </body>
        </html>
      );
    }
    ```
  </Tab>

  <Tab value="Next.js Pages Router">
    ```tsx title="pages/_app.tsx"
    import Script from "next/script";
    import type { AppProps } from "next/app";

    export default function App({ Component, pageProps }: AppProps) {
      return (
        <>
          <Component {...pageProps} />
          <Script
            src="https://www.annot8.app/widget.js"
            data-project-id={process.env.NEXT_PUBLIC_ANNOT8_PROJECT_ID}
            data-convex-url={process.env.NEXT_PUBLIC_ANNOT8_CONVEX_URL}
            strategy="afterInteractive"
          />
        </>
      );
    }
    ```
  </Tab>

  <Tab value="Nuxt">
    ```ts title="nuxt.config.ts"
    export default defineNuxtConfig({
      app: {
        head: {
          script: [
            {
              src: "https://www.annot8.app/widget.js",
              async: true,
              "data-project-id": process.env.NUXT_PUBLIC_ANNOT8_PROJECT_ID,
              "data-convex-url": process.env.NUXT_PUBLIC_ANNOT8_CONVEX_URL,
            },
          ],
        },
      },
    });
    ```
  </Tab>

  <Tab value="SvelteKit">
    ```html title="src/app.html"
    <body data-sveltekit-preload-data="hover">
      <div style="display: contents">%sveltekit.body%</div>
      <script
        src="https://www.annot8.app/widget.js"
        data-project-id="pj_xxxxxxxxxxxx"
        data-convex-url="https://your-deployment.convex.cloud"
        async
      ></script>
    </body>
    ```
  </Tab>

  <Tab value="WordPress">
    Add the snippet to your theme's `footer.php` just before `</body>`, or paste it
    into a "custom scripts" field if your theme or a plugin such as WPCode provides
    one. Avoid pasting it into a post — it needs to run on every page.
  </Tab>
</Tabs>

## Content Security Policy [#content-security-policy]

If your site sends a CSP, the widget needs four allowances:

```
script-src  https://www.annot8.app;
connect-src https://*.convex.cloud https://*.convex.site https://www.annot8.app;
frame-src   https://www.annot8.app;
img-src     data: blob:;
```

Why each one:

* **`script-src`** — the loader itself.
* **`connect-src`** — the widget talks to your Convex deployment over HTTPS and
  WebSocket, and calls `…convex.site/widget/session` to mint an origin-validated
  session token. Analytics, if enabled, posts a session-end beacon to the
  dashboard origin.
* **`frame-src`** — all widget UI renders inside a sandboxed iframe served from
  the dashboard origin. Without this the launcher never appears.
* **`img-src data: blob:`** — screenshots are rendered to a canvas in the page
  before upload.

## Verify it worked [#verify-it-worked]

You should see a launcher button in the corner within a second or two of load.
If it isn't there, work through [Troubleshooting](/docs/troubleshooting) — the
overwhelmingly common cause is a hostname that matches no environment.

To confirm from the console:

```js
typeof window.Annot8 === "function"; // loader present
```

The loader being present but no launcher appearing means the project or
environment check rejected the page, not that the script failed to load.

## Privacy and consent [#privacy-and-consent]

Core feedback collection does not require Annot8 Analytics, and Analytics is off
for new projects. It only starts in the browser when both are true:

1. An admin enabled **Analytics** in **Settings → Features**.
2. Your page passes `data-analytics-consent="true"`.

Wire that attribute to your consent-management platform rather than hard-coding
it, and describe Annot8 in your own privacy and cookie notices. See
[Privacy and data handling](/docs/security/privacy).

## Next [#next]

<Cards>
  <Card title="Configuration reference" href="/docs/widget/configuration">
    Every attribute and runtime method.
  </Card>

  <Card title="Environments" href="/docs/dashboard/environments">
    Control which hosts the widget runs on.
  </Card>
</Cards>
