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

If you have MCP tools that read or write user-owned data (orders, tickets, settings), you need signed-in mode. A public widget calling those tools would operate as a single shared account, not as the logged-in visitor.

Public widget

Paste this before </body>. No backend required.

html
<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.

html
<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

The loader fetches accent, launcher, position, placeholder, display mode, and theme from the widget's dashboard settings on every page load — changing them there updates every page that has the snippet installed within about 30 seconds, with no code changes. The attributes below still work as optional per-page overrides: any attribute you set on the script tag wins over the dashboard config for that page only.

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, or investigations. Which assistant mode the chat starts in. Optional — defaults to the first mode enabled on the widget.
  • data-cmdk-theme — light, dark, or auto (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 — right or left. Optional — defaults to the widget's dashboard setting.
  • data-cmdk-launcher — bubble, bar, pill, avatar, tab, embed (full-page assistant), or custom (nothing injected — open from your own UI via window.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, or fullpage. Optional — defaults to the widget's dashboard setting.
  • data-cmdk-panel-width — width in pixels for inline mode, 320–720 (default 420). 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 for bar — it's page-specific and can't come from the dashboard.
  • data-cmdk-locale — en or tr. 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

The 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.tsx inside a <Script strategy="beforeInteractive"> or plain <script> in <head>.
  • Vite / CRA: add it to index.html before </body>.
  • Remix: add to root.tsx inside the <body> of your Layout.
  • Webflow / Framer / Squarespace: use the site-wide custom code injection field.

Warning

Don't wrap the script in React's <Helmet> — some Helmet versions strip data-* attributes. Use a plain dangerouslySetInnerHTML or a <Script> component instead.

Verify the install

  1. Open your site in an incognito window.
  2. You should see the launcher button (bubble / bar / pill) appear.
  3. Click it — the chat panel should open within ~200 ms.
  4. 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.