<script> tag. It loads a small loader (widget.js) from your desk, draws the button or island, and loads the chat itself only when it’s needed. Everything about its look comes from the desk, so you paste the snippet once and change colours, wording and mode later without touching your site.
Copy the snippet
1
Open the Install tab
In the desk, go to Chat widget → Install (
/appearance/install). The Install the chat widget card shows the snippet with your desk’s address and your widget key filled in. Press Copy, or Send to a developer to copy the snippet with a few plain lines on where it goes and how to check it.Until the widget is on a site, the sidebar’s Chat widget entry has a small dot and opens on this tab, and the page header says Not on your site yet. The Get started checklist’s Put it on your site step shows the same snippet.2
Paste it into your site's head
Put it inside
<head>, before your own scripts:data-key is your widget key. It is public by design: anyone can read it from your page source. Use Allowed websites to stop other sites reusing it.3
Deploy your site and open a page
On a site builder or framework, Where to paste it on the same tab says where the head code goes in Webflow, WordPress, Shopify and Next.js.The widget appears within a second or so (the loader waits up to 1.5 seconds for your colour and mode settings, so it never flashes in the default colour).
Why defer, in <head>, before your scripts
The loader is written to run early, and the placement matters for two features:
- Debug context. The loader listens for JavaScript errors and failed requests from the moment it runs. In
<head>it sees problems from the start of the page load. See Debug context. - Page actions. If the browser has no native WebMCP, the loader installs a stand-in
document.modelContextso your code can register actions. See Page actions.
defer doesn’t hold up the page, and deferred scripts (module bundles too) run in document order. So when the snippet comes before your app’s bundle, the loader has already run when your code starts, and document.modelContext exists when your code checks for it.
Snippet attributes
All attributes go on the same<script> tag. The full list with every JunDesk.* method is in the JavaScript API.
Frameworks
The rule is the same everywhere: the tag must end up in the document’s<head>, with defer, ahead of your app’s own scripts. Add it to the HTML your server sends, not from JavaScript after the page has loaded.
- Plain HTML
- Vite or other SPA
- Server-rendered templates
index.html
The loader runs only once per page: if
window.JunDesk already exists, a second copy does nothing. Single-page apps don’t need to re-add it on route changes. It follows history.pushState, replaceState and popstate itself.useJunAction from packages/widget-react registers page actions while a component is mounted and waits for the loader if needed. See Page actions.
Check that it works
1
Try the demo page first
Chat widget → Install has an Open demo page button. It opens
/demo.html?key=wk_… on your desk: a fake customer site with buttons that fail on purpose (a 500 on “Pay invoice”, a JavaScript error on “Show usage chart”, a dead “Export CSV”), a cancel intent and three page actions. Use it to see the whole loop before touching your own site.2
Send a message from your site
Open your site, open the chat, and write something. Open the desk’s Inbox in another window. The message arrives live, with a Browser section in the side panel showing the page and recent events.
3
Look for yourself on the visitor list
Visitors shows everyone on your site right now. Your own tab should be there (unless you use
data-consent="required" and haven’t consented yet).Install detection
The first time the loader connects from a site other than the desk itself, the desk notes that site. The Install tab’s Waiting for your site… line turns into Seen on with that site (it checks every few seconds while the tab is open), the header on every tab of Chat widget shows Live on that site, the sidebar dot goes away, and the Put it on your site step in Get started ticks itself. The demo page doesn’t count, because it runs on the desk’s own origin. The check rides on the loader’s live connection. Withdata-consent="required", nothing connects until a visitor consents, so the step may not tick on its own. Press I’ve added it (on the Install tab or in Get started) to mark it by hand.
If nothing appears:
- Open the browser console.
Jun: no data-keymeans the attribute is missing. - Check the network tab for
widget.jsand/api/widget/<key>/config. A 404 onconfigmeans the key doesn’t match this desk. - If you set Allowed websites, check your site’s hostname is on the list (see below).
Limit which websites can use the widget
Because the key is public, anyone could copy your snippet onto their own site. Under Chat widget → Behaviour → Allowed websites, list your domains, separated by commas or spaces:*.acme.comcovers every subdomain andacme.comitself.- You can paste full URLs; the desk keeps only the hostname.
- Up to 50 entries. An empty list means any website, which is handy while you try things out.
- The desk’s own address always works, so the demo page and the Appearance preview keep working.
frame-ancestors policy), the live visitor connection is refused, and proactive nudges aren’t offered there.
Next steps
Pick a mode
The island, or a chat window with or without a greeting card.
Set the look
Colour, logos, greeting and suggested questions.
Identify signed-in users
Verified customers, chats across devices.
Page actions
Let the AI do things on your page.