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

# Installing the widget

> The script tag, its attributes, and the npm package.

## The script tag

```html theme={null}
<script
  src="https://cdn.peeve.ai/widget.js"
  data-publishable-key="pk_live_xxx"
  defer
></script>
```

That is the complete install. The tag auto-boots on `DOMContentLoaded` — you do
not need to call `init()`.

The key belongs in the page. It is a publishable key, public by construction;
what protects it is [the origin check](/authentication/origins), not secrecy.

<Warning>
  Never put a secret key (`sk_…`) in this tag. The widget endpoints reject
  secret keys, so it would not work — and it would publish your credential to
  every visitor.
</Warning>

With no key on the tag, the widget logs one warning and does nothing. It never
throws into your page.

## Where to put it

On every page you want the agent to work on, including the ones it needs to
navigate *through*. The agent can only act on controls that are on screen, so a
page without the widget is a page it cannot walk a user into.

`defer` is recommended — the widget never blocks rendering.

## Script tag attributes

Everything below is optional.

### Configuration

| Attribute              | Purpose                                                                                                                |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `data-publishable-key` | Your publishable key. **Required.** `data-workspace-key` and `data-key` are accepted aliases.                          |
| `data-name`            | The agent's display name — the panel reads "Ask {name}". Overridden by the name set in the dashboard, if there is one. |
| `data-suggestions`     | Starter chips for the ask panel, pipe-separated: `"Change my plan\|Invite a teammate"`.                                |
| `data-lang`            | Locale hint. Falls back to your `<html lang>`, then the browser's language.                                            |
| `data-cancel-selector` | A CSS selector that force-marks a control as a cancel action, feeding churn-signal detection.                          |
| `data-endpoint`        | Override the API origin. For a reverse-proxied or self-hosted platform.                                                |

### Identity on the tag

If your server renders the page and already knows who the user is, you can put
identity straight on the tag instead of calling `identify()`:

```html theme={null}
<script
  src="https://cdn.peeve.ai/widget.js"
  data-publishable-key="pk_live_xxx"
  data-user-id="user_8412"
  data-user-email="dana@example.com"
  data-user-name="Dana Whitfield"
  data-user-plan="Growth"
  data-user-created-at="2026-02-14T09:31:00Z"
  defer
></script>
```

Available: `data-user-id`, `data-user-email`, `data-user-name`,
`data-user-company`, `data-user-plan`, `data-user-phone`,
`data-user-created-at`. Empty attributes are ignored.

<Note>
  A programmatic `identify()` call wins over the tag. The tag's identity is
  adopted only when `identify()` has not already recorded one — so an
  interactive login updates identity correctly without you clearing anything.
</Note>

## Configuring with a global instead

If setting attributes is awkward in your framework, set `window.PeeveConfig`
before the script loads:

```html theme={null}
<script>
  window.PeeveConfig = {
    publishableKey: "pk_live_xxx",
    name: "Guide",
    user: { id: "user_8412", email: "dana@example.com" },
  };
</script>
<script src="https://cdn.peeve.ai/widget.js" defer></script>
```

Precedence for the key: an explicit `init()` option, then the script tag
attribute, then `PeeveConfig`.

## The npm package

```bash theme={null}
npm i @peeve/sdk
```

```js theme={null}
import { Peeve } from "@peeve/sdk";

Peeve.init({ publishableKey: "pk_live_xxx" });
```

`Peeve.init` injects the same script tag from the same CDN with the same key. It
is idempotent, and it will not add a second tag if one is already on the page —
so having both is harmless.

The package works with any frontend and has no dependencies. It is SSR-safe:
`init` is a no-op on the server, and `identify` / `reset` calls made before the
widget finishes loading are queued and flushed when it appears.

<Note>
  There are two names here and they are not the same object. `Peeve` (capital)
  is what the npm package exports. `window.peeve` (lowercase) is the running
  widget on the page. Both are covered in
  [the JavaScript API](/widget/javascript-api).
</Note>

## Verifying the install

<Steps>
  <Step title="Check the widget booted">
    In the browser console, `window.peeve` should be defined. If it is not, the
    script did not load or the tag had no key — check for the
    `[peeve] no data-publishable-key…` warning.
  </Step>

  <Step title="Check the config call">
    In the network tab, look for the widget's bootstrap request to
    `api.peeve.ai`. A `200` with `"ok": true` means the key and origin
    resolved.

    If `enforcement.disabled` is `true`, the workspace is not entitled to serve —
    check the kill switch, the trial expiry and the subscription.
  </Step>

  <Step title="Check it has been mapped">
    If `mapped` is `false` in that response, the widget stays hidden for end
    users on purpose — a real visitor should never meet an unmapped cursor. Run
    the baseline capture from the dashboard.
  </Step>
</Steps>

## Content Security Policy

If your app sets a CSP, allow:

```
script-src  https://cdn.peeve.ai
connect-src https://api.peeve.ai
```

The widget loads an additional chunk from the same origin as `widget.js`, which
is why `cdn.peeve.ai` must be in `script-src` and not only `connect-src`. API
calls go to `api.peeve.ai`.

If you set `data-endpoint`, add that origin to `connect-src` instead.
