How options are set
Every option has two spellings — adata-* attribute on the script tag, and a
camelCase key passed to init(). They are the same option:
- Booleans are true unless the value is exactly
"false"or"0". A baredata-search-mode-enabledwith no value istrue. - 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: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.
Modal chrome
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.Consent
Hand-off to a human
Call to action
Surveys
Branding and footer
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
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.