# Installing Produktly

Produktly is installed with a single script snippet. There is no npm package — the script tag is the only distribution.

## The snippet (canonical)

Add this to your app's `<head>` (or end of `<body>`), on every page where widgets should run. Replace `YOUR_CLIENT_TOKEN` with the Client Auth Token from the Produktly dashboard → Settings → Installation (https://produktly.com/app/settings/installation).

```html
<script>
  (function (w, d, f) {
    w.Produktly=w.Produktly||new Proxy(function(){},{get:function(_,n){return function(){w.ProduktlyQ=(w.ProduktlyQ||[]).concat([[n,Array.prototype.slice.call(arguments)]])}},apply:function(_,__,a){w.ProduktlyQ=(w.ProduktlyQ||[]).concat([["apply",a]])}})
    var a = d.getElementsByTagName('head')[0];
    var s = d.createElement('script');
    s.async = 1;
    s.src = f;
    s.setAttribute('id', 'produktlyScript');
    s.dataset.clientToken = "YOUR_CLIENT_TOKEN";
    a.appendChild(s);
  })(window, document, "https://public.produktly.com/js/main.js");
</script>
```

Do not remove the first line (the `ProduktlyQ` queue): it makes `window.Produktly.*` calls safe before the script finishes loading — calls are queued and replayed automatically.

The loader injects a `<script id="produktlyScript">` element carrying your token as a `data-client-token` attribute — that's how the widget finds it, so keep the `id` and `s.dataset.clientToken` lines exactly as-is.

Options:
- Load after other content: replace `s.async = 1;` with `s.defer = 1;`.
- Wait for user identification before showing widgets (recommended for logged-in apps): add `s.dataset.waitForUser = "true";` after the clientToken line. Widgets then stay hidden until `Produktly.identifyUser(...)` runs.

## Two different tokens — do not confuse them

| | Client Auth Token | Private API key |
|---|---|---|
| Purpose | Loads the widget script on your site | REST API + MCP server auth |
| Secrecy | Public (sits in your HTML) | Secret — backend/agent only, never in frontend code |
| Where | Settings → Installation | Settings → Private Keys |

## Framework notes

- **React (CRA/Vite)**: put the snippet in `public/index.html` / `index.html`, or run the IIFE once in a top-level `useEffect(() => { ... }, [])`. The script is async and does not block hydration.
- **Next.js (app router)**: wrap the IIFE body in `<Script id="produktly" strategy="afterInteractive">{`...`}</Script>` from `next/script` in `app/layout.tsx`. Pages router: same via `pages/_document.js`.
- **Vue**: `public/index.html`, or in `main.js` before `app.mount()`.
- **Google Tag Manager**: Custom HTML tag with the full snippet, trigger All Pages, tag firing option "Once per page". SPAs do NOT need a History Change trigger. Don't combine GTM install with a hardcoded snippet — pick one.
- **WordPress**: use the official plugin (https://wordpress.org/plugins/produktly/) — no code needed.
- **SPAs in general**: install once per page load; the widget tracks route changes itself.

## Content-Security-Policy

If your site sets a CSP: `script-src` needs `https://public.produktly.com`; `connect-src` needs `https://api.produktly.com` and `https://public.produktly.com`.

## Verify the install

Open the site, run `window.Produktly` in the console (should be an object after load), and `window.Produktly.debug()` to enable verbose logging while testing.
