This is the complete reference. For installing the widget in the first place, see Ask AI Widget; for the per-platform guides, start at Install on your platform.

How options are set

Every option has two spellings — a data-* attribute on the script tag, and a camelCase key passed to init(). They are the same option:
Three rules govern every attribute in the tables below:
  • Booleans are true unless the value is exactly "false" or "0". A bare data-search-mode-enabled with no value is true.
  • An absent attribute is not false. It leaves the documented default in place, which matters for the options that default to on.
  • An empty string means “leave the default”, so a template that renders data-product-name="" when it has no name does no harm.

Required

Both are in the dashboard under Knowledge Base → Integration. Without them the script loads and does nothing.

Identity and endpoint

data-language changes the widget’s own buttons and labels. It does not steer the answer — the model replies in the language the question was asked in.

Layout

Launcher

In your own header

An “Ask AI” button beside the search box, where a reader who has just failed to find something is already looking. Give the widget a selector for something already in your header and it mounts the launcher next to it:
The launcher becomes a 36px labelled button with no drop shadow and no animations, sized and shaped to sit in a row of header controls. It keeps its own shadow root, so your header’s button styles cannot reach it. Every match gets a button, not just the first. Most docs themes render a desktop header and a mobile header as separate markup, both always in the DOM, each hidden at the other’s breakpoint — anchoring only the first would leave phones with no way into the widget. The example above is Mintlify’s two search entries. Anchors are watched for as long as the page lives, so a header that renders after hydration, or is replaced on every client-side navigation, still gets its button back. If the selector matches nothing within data-launcher-button-anchor-wait-ms, the launcher falls back to the corner and says so in the console — a button in the wrong place beats no button at all.
This is the inverse of an open trigger: a trigger binds a button you render, this renders the button for you. Use a trigger when you control the header markup and want your own styling; use an anchor when you do not — a hosted docs theme, someone else’s template.

Opening

Open triggers

Use your own “Ask AI” button instead of the launcher. Each trigger accepts a selector, a class list, or an id list — three spellings of the same thing: The camelCase equivalents are openSelector, openSelectorAsk and openSelectorSearch, each taking one selector. Triggers are bound by delegation on document, so a button your framework re-renders on every route change keeps working without re-initialising. data-open-query-from is what stops the reader typing the question twice: they typed it into the search box, then pressed the button beside it.

Conversation and content

Search tab

A second tab that lists matching pages and generates no answer — cheap, instant, and often what the reader actually wanted.

Retrieval scoping

Always intersected server-side with the client key’s own allowed_groups, so this can narrow what a reader reaches but never widen it. setSourceGroupIds() changes it at runtime — for a docs site that scopes the assistant to the section being read.

MCP menu

Left unset, the menu stays hidden unless the server reports mcp_available — a client key with an origin allowlist needs mcp_enabled before MCP clients can use it, and printing install commands that would 403 helps nobody. Setting data-mcp-enabled="true" overrides that check, for self-hosted or local runs. See MCP server.

Hand-off to a human

Call to action

Surveys

hideBranding and brandingText are Enterprise-plan only and verified server-side through widget-config (hide_branding_allowed). On every other plan the footer stays visible whatever the attribute says.data-privacy-links is JSON because the entries are pairs — every flat encoding of [{title,url}] in an attribute breaks on the first title containing a comma. Invalid JSON logs a warning and is ignored.

Analytics identity

The stored id is random — it identifies a browser, not a person.The fingerprint is deliberately low-entropy: user agent, language, timezone and screen size, and nothing else. No canvas, no font probing, no audio. It is still a fingerprint, which is why it is opt-in and named plainly — turning it on is a decision about your own privacy policy. See PII and data retention.

Bot protection

Only meaningful where captcha is configured server-side; the widget reads that from widget-config.

Theme tokens

Every color has a …Dark twin, used when the panel is in dark mode. The light value is deliberately not reused there: a brand surface chosen against white is usually unreadable against black. Append -dark to the attribute (data-surface-color-dark) or Dark to the option (surfaceColorDark).

Type and sizing

Theme values land inside a stylesheet, so they are validated before use: anything containing ;, {, } or url( is dropped. Styles are otherwise isolated in a Shadow DOM, so nothing here leaks into your page and your CSS does not leak in.

Form deflector

The same script tag can arm a support form deflector. data-deflector-id alone is enough — the wiring is fetched at runtime, so a form whose markup changes is a dashboard edit rather than a redeploy of your support page. Anything set on the tag overrides the stored value, which is what makes a one-page exception possible.

Migrating from kapa.ai

Where an option has two attribute spellings, the second is the one an existing kapa embed already carries. Both work, so a migrated tag can be pasted across unchanged and edited later: The event names and payloads match as well, and there is a callable BeforeQuery("onModalOpen", handler) form alongside BeforeQueryWidget.on(), so existing analytics wiring needs no rewriting.