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.
Two placements at once
An anchor replaces the floating button, on the grounds that two ways into one panel is one too many.data-launcher-button-floating overrides that and you
get both:
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
The expand button
The panel header has a ⤢ button. The reader presses it to make the panel bigger, and ⤡ to put it back. What “bigger” means depends on the view mode:- modal — the panel grows from
modalSize(760px) tomodalExpandedSize(1500px), and to the full height of the window. - sidebar — the panel leaves the right edge and becomes that same large centred panel. Pressing ⤡ docks it back to the edge.
search view mode,
when fullScreen is on, and on screens under 600px wide (there the panel
already fills the screen). If a reader expands on a laptop and then makes the
window narrow, their choice is kept for when it is wide again.
To turn it off, or to change how big “expanded” is:
expand() and collapse().
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, fast, and often what the reader actually wanted. It searches on Enter, not on every keystroke, and Enter never sends the query to the chat: a reader typing into a box labelled Search gets a list of pages. Arrow keys walk that list and Enter opens the highlighted row. The AI is one click away on the “Ask AI: …” row above the results — the rowhideSearchAskCta removes.
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.
The full reference, including the fields stored on the deflector itself, is in
Behaviour and
Styling.
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.