How do I install the CMD+K widget?
Drop one <script> tag into your page and the CMD+K widget is live in under a minute. For anonymous public tools no backend is needed at all. If your MCP tools need to know who is asking — showing a user their own orders, tickets, or data — switch to signed-in mode and pass a short-lived JWT your backend mints per visitor.
Step 0: choose an auth mode
Before pasting the snippet, decide how visitors will be identified. You can change this any time from the Install tab.
- Public — anyone can chat anonymously. The assistant has no idea who is asking. Good for docs, landing pages, and demos.
- Signed-in only — every chat request carries a JWT your backend signed for that visitor. The assistant knows exactly who is asking, so connected MCP tools can act on their account — not a shared one.
Note
Public widget
Paste this before </body>. No backend required.
<script async
src="https://commandplusk.com/embed/widget.js"
data-cmdk-widget="<WIDGET_ID>">
</script>Signed-in widget
Same snippet, plus data-cmdk-token set to a JWT your backend mints for the current visitor.
<script async
src="https://commandplusk.com/embed/widget.js"
data-cmdk-widget="<WIDGET_ID>"
data-cmdk-token="<USER_TOKEN>">
</script><USER_TOKEN> is a short-lived HS256 JWT signed with your widget's secret key. See End-user authentication for the full signing guide.
Style config now lives in the dashboard
All snippet attributes
data-cmdk-widget— required. Your widget UUID from the dashboard.data-cmdk-token— signed-in mode only. Short-lived JWT for the current visitor.data-cmdk-mode—assistant,onboarding,learning, orinvestigations. Which assistant mode the chat starts in. Optional — defaults to the first mode enabled on the widget.data-cmdk-theme—light,dark, orauto(follows the visitor's OS). Optional — defaults to the widget's dashboard setting.data-cmdk-accent— hex color override (e.g.#6366f1). Optional — defaults to the widget's dashboard setting.data-cmdk-position—rightorleft. Optional — defaults to the widget's dashboard setting.data-cmdk-launcher—bubble,bar,pill,avatar,tab,embed(full-page assistant), orcustom(nothing injected — open from your own UI viawindow.CommandK.open()). Optional — defaults to the widget's dashboard setting. Each launcher is described with a snippet on Launchers & display modes.data-cmdk-display—overlay,inline, orfullpage. Optional — defaults to the widget's dashboard setting.data-cmdk-panel-width— width in pixels for inline mode, 320–720 (default420). A width the visitor saved by dragging the panel edge takes precedence.data-cmdk-placeholder— input placeholder text (bar/pill/embed launchers). Optional — defaults to the widget's dashboard setting.data-cmdk-target— CSS selector for the bar launcher mount point (e.g.#cmdk-mount). Always required forbar— it's page-specific and can't come from the dashboard.data-cmdk-locale—enortr. Language of the guided-tour chrome (Next, Skip, Done…). Optional — defaults to the visitor's browser language.data-cmdk-embed-key— partner embeds only. A per-widget key that bypasses the origin allowlist so a third-party product can host your widget from any origin. See Partner embeds.
The full attribute table with values and defaults lives on Launchers & display modes.
The inline bar is a command palette
bar launcher mounts an in-page ⌘K palette. Focusing it opens a dropdown of your configured starter prompts; typing filters them and offers to ask the free-text query. Arrow keys navigate, ↵ runs the highlighted item (or the typed text), and ⌘K/Ctrl+K focuses it from anywhere. Selecting an item opens the assistant and sends the message. Configure the starter prompts under the assistant's Overview → Suggested prompts.Launchers and display modes
The widget can open as a floating overlay (default), a docked inline side panel that pushes your content aside and can be resized by the visitor, or a fullpage takeover with a conversation sidebar — and it can be triggered by a corner bubble, an avatar, a side tab, a bottom pill, an in-page ⌘K bar, or your own button (custom). Every combination, with a copy-paste snippet and when to use it, is documented on Launchers & display modes; programmatic control (window.CommandK.open() and friends) is covered there and on the postMessage host API page.
Framework tips
- Next.js App Router: add the script to
app/layout.tsxinside a<Script strategy="beforeInteractive">or plain<script>in<head>. - Vite / CRA: add it to
index.htmlbefore</body>. - Remix: add to
root.tsxinside the<body>of yourLayout. - Webflow / Framer / Squarespace: use the site-wide custom code injection field.
Warning
<Helmet> — some Helmet versions strip data-* attributes. Use a plain dangerouslySetInnerHTML or a <Script> component instead.Verify the install
- Open your site in an incognito window.
- You should see the launcher button (bubble / bar / pill) appear.
- Click it — the chat panel should open within ~200 ms.
- In signed-in mode, send a message and confirm the conversation appears in the dashboard with the correct visitor
sub.
Next steps
FAQ
- Does the script block page load?
- No. The async attribute defers loading until after the page is interactive.
- What's the bundle size?
- The loader is ~5 KB. The full widget iframe only downloads when a visitor opens the chat.
- Can I use a custom launcher button?
- Yes. Send window.postMessage({ type: 'cmdk:host:open' }, '*') from any button click to open the widget programmatically.
- Can I hide the widget on certain pages?
- Send window.postMessage({ type: 'cmdk:host:close' }, '*') or conditionally omit the script tag on those routes.
- What happens if a visitor's JWT expires while they're chatting?
- The widget shows a 'Your session expired. Please refresh the page.' notice and stops sending. The token is read once from data-cmdk-token at page load (there is no refresh event in the v1 protocol), so mint a short-lived JWT on every page render and size its expiry to a realistic session length.