AgentStack Docs

Embed the Chat Widget

Add the AgentStack chat bubble to any website — plain HTML or a framework — and avoid the two mistakes that make it silently fail to appear.

The chat widget is two <script> tags. The hard part isn't the snippet — it's putting it where it actually renders, and configuring the allowed domains correctly. Get those two things right and the bubble shows up everywhere.

The snippet

Copy this from your agent's Deploy → Chat Widget page (it already has your agent ID baked in):

<!-- AgentStack Widget -->
<script>
  window.agentstack=new Proxy({_q:[]},{get(t,p){
    if(p==='_q')return t._q;return(...a)=>t._q.push([p,...a]);
  }});
</script>
<script src="https://www.agentstack.build/embed.js" data-agent-id="YOUR_AGENT_ID" async></script>

Replace YOUR_AGENT_ID with the ID shown on the Deploy page.

Let your coding agent install it

If you use Claude Code, Cursor, or another AI coding agent, paste the prompt below. It tells the agent how to detect your framework and place the snippet in the one file that wraps every page — the most common reason the widget never shows is that it gets added to a file that doesn't actually render on the public site.

Install the AgentStack chat widget on this website.

The widget is these two script tags (keep them together, in this order):

  <script>
    window.agentstack=new Proxy({_q:[]},{get(t,p){
      if(p==='_q')return t._q;return(...a)=>t._q.push([p,...a]);
    }});
  </script>
  <script src="https://www.agentstack.build/embed.js" data-agent-id="YOUR_AGENT_ID" async></script>

CRITICAL — place them in the file that wraps EVERY page that real visitors load,
not just any layout file. First detect the stack, then use the matching target:

- Plain HTML / static site: add both tags just before </body> in every page
  (or the shared template/partial/include if the site uses one).

- Next.js — detect the router FIRST. A project can contain BOTH src/app/ and
  src/pages/; what matters is which one renders the route visitors hit (often "/").
    • If the homepage is served by the Pages Router (a pages/index.* or
      src/pages/index.* file exists, OR the rendered page exposes
      window.__NEXT_DATA__): put the widget in pages/_app.tsx (or src/pages/_app.tsx),
      rendered via next/script with strategy="lazyOnload".
    • If the homepage is served by the App Router (app/page.* or src/app/page.*
      exists and there is NO pages/index.*): put it in app/layout.tsx inside
      <body>, via next/script with strategy="lazyOnload".
    • Do NOT assume App Router just because a src/app/layout.tsx exists — if there
      is no app/page.* and the routes live under pages/, that layout never renders.

- React (Vite/CRA, no SSR): add the two raw tags to index.html before </body>.
  Do not convert them to JSX in a component.

- Vue / Nuxt: Nuxt → add to app.vue or nuxt.config (app.head script). Plain Vue
  → index.html before </body>.

- SvelteKit: src/app.html before %sveltekit.body% or just before </body>.

- Astro: the base layout in src/layouts/ used by every page, before </body>.

- WordPress / other CMS: the theme footer template, or a "scripts in footer"
  setting / plugin.

When the framework provides a script primitive (next/script, Nuxt useHead, etc.),
prefer it over a raw tag and load the script lazily/after-interactive — the widget
is non-critical and should not block render.

After installing, verify (see the checklist in the AgentStack embed docs):
1. Load the public site and confirm window.agentstack is defined (not undefined).
2. Confirm a network request to https://www.agentstack.build/embed.js returns 200.
3. Confirm the chat bubble renders, and clicking it opens a working chat panel.

Report which framework you detected and which file you edited.

Swap YOUR_AGENT_ID in the prompt for your real agent ID before sending it.

Domain protection: do not use *. wildcards

On the Deploy page you can restrict which domains may embed your agent. This is where fresh installs most often break. The matcher supports exact hostnames and automatic subdomain coverage — it does not support *. glob wildcards.

You want to allowEnter thisDon't enter
example.com and all its subdomains (www., app., …)example.com*.example.com
Only app.example.comapp.example.com
Local developmentlocalhost

Rules:

  • A bare domain already covers its subdomains. example.com matches example.com, www.example.com, app.example.com, and so on. You do not need a separate wildcard line.
  • *.example.com matches nothing. The literal *. is stored as-is and never matches a real hostname, so the chat panel is blocked on every domain.
  • Enter the host only — no https://, no www. prefix needed, no trailing path. https://example.com/ will not match; example.com will.
  • Leave the field empty to allow all domains. Only fill it in when you want to restrict embedding.

Verify the install

  1. Open your public site (the real domain, not a preview URL — domain protection blocks hosts that aren't on the allow-list).
  2. Open DevTools → Console and run window.agentstack. If it's undefined, the snippet isn't on the page → it's in the wrong file (see the framework list above).
  3. DevTools → Network: embed.js should return 200.
  4. The bubble should appear (bottom-right by default). Click it — the chat panel should load. A bubble that opens to an empty/error panel almost always means a domain-protection mismatch (the *. wildcard mistake above).

Troubleshooting

SymptomLikely causeFix
No bubble at all; window.agentstack is undefinedSnippet in a file that doesn't render on the public site (e.g. Next.js App Router layout while the site uses the Pages Router)Move it to the file that wraps every page
No bubble; window.agentstack is definedAgent isn't public, or geo-restrictedToggle the agent public on the Deploy page; check geo settings
Bubble appears but panel is empty / "not allowed on this domain"Domain protection mismatch — usually a *. wildcard or a https://-prefixed entryUse the bare hostname; remove *. and scheme
Works locally, not in productionlocalhost allowed but production domain isn't on the listAdd the production hostname (bare)

On this page