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 is not the snippet. The hard part is where you place it, and how you configure the allowed domains. If you get both steps right, the bubble shows up everywhere.

The snippet

Copy the snippet from your agent's Deploy → Chat Widget page. This page already includes your agent ID.

<!-- 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. The prompt tells the agent how to detect your framework and place the snippet in the one file that wraps every page. Most installs fail because the snippet ends up in a file that does not 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 you send it.

Domain protection: do not use *. wildcards

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

You want to allowEnter thisDo not 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 system stores the literal *. as plain text. It never matches a real hostname. As a result, no domain can open the chat panel.
  • Enter the host only. Do not add https://. Do not add a www. prefix or a trailing path. https://example.com/ does not match. example.com matches.
  • Leave the field empty to allow all domains. If you want to restrict embedding, enter a domain in the field.

Verify the install

  1. Open your public site. Use the real domain, not a preview URL. Domain protection blocks hosts that are not on the allow list.
  2. Open DevTools → Console. Run window.agentstack. If the result is undefined, the snippet is not on the page. The snippet is in the wrong file. See the framework list above.
  3. Open DevTools → Network. embed.js must return 200.
  4. The bubble appears in the bottom-right corner by default. Click it. The chat panel loads. When a bubble opens to an empty panel or an error panel, this usually means a domain-protection mismatch. See the *. wildcard mistake above.

Troubleshooting

SymptomLikely causeFix
No bubble appears, and window.agentstack is undefinedThe snippet is in a file that does not render on the public site (for example, a Next.js App Router layout when the site uses the Pages Router)Move the snippet to the file that wraps every page
No bubble appears, but window.agentstack is definedThe agent is not public, or the agent is geo-restrictedMake the agent public on the Deploy page. Check the geo settings.
The bubble appears, but the panel is empty or shows "not allowed on this domain"Domain-protection mismatch, usually a *. wildcard or an entry with a https:// prefixUse the bare hostname. Remove the *. and the scheme.
The widget works locally, but not in productionlocalhost is on the allow list, but the production domain is notAdd the bare production hostname

On this page