Widget SDK
The browser SDK mounts Datalumo search and chat on your site. It loads presentation settings from your widget, streams answers, stores chat history in the browser, and reports analytics. For dashboard settings see Chat and Search. For custom UIs or server-side calls, use the Widget API instead. To connect a chatbot to your own systems, see Chat actions.
Script URL:
https://datalumo.app/widget/v1/datalumo.js
The SDK exposes a global Datalumo object. The API origin is taken from the script URL, so one build works on any host.
#Before you embed
- Create a search or chat widget in the dashboard.
- Add your site domain under the widget's websites list (for example
example.comorwww.example.com). Browser requests are authorised by the page origin. - Copy the widget key. It looks like
{organisation}/{widget}(two public ids). It is safe to put in HTML.
You can also copy a ready-made snippet from the widget page under Add to your site.
#Install
Paste before </body>:
#Chat bubble
<script src="https://datalumo.app/widget/v1/datalumo.js"></script>
<script>
Datalumo.chat('ORG_ID/WIDGET_ID').mount();
</script>
#Search (modal trigger in a container)
<!-- min-height matches the launcher so content below does not jump while the SDK loads -->
<div id="search" style="min-height: 2.5rem"></div>
<script src="https://datalumo.app/widget/v1/datalumo.js"></script>
<script>
Datalumo.search('ORG_ID/WIDGET_ID', { target: '#search' }).mount();
</script>
Load the script once per page. Call .mount() after the DOM nodes you pass as target or trigger exist.
Avoid layout shift: give the search target a fixed min-height of 2.5rem (the launcher height) in your HTML. The SDK also reserves that height on mount() when the target is still empty, which covers the config-fetch gap after the script runs. Pre-sizing in HTML is still best so nothing moves before JS runs.
#Entry points
| Call | Use when |
|---|---|
Datalumo.chat(key, options?) |
Chatbot widget (bubble, sidebar, or inline) |
Datalumo.search(key, options?) |
Search widget (modal or inline) |
Datalumo.headless(key, options?) |
No UI — search, chat, events, and conversation APIs yourself |
Datalumo.markdown(text) |
Turn markdown from chat/summarize into sanitised HTML (headless UIs) |
Every call returns a handle. For chat and search, call .mount() to start. Headless is ready immediately.
#Shared options
These apply to chat(), search(), and headless():
| Option | Type | Description |
|---|---|---|
user |
{ id, hash? } |
Tag the visitor. With a server-signed hash, conversation history and ownership work (see Identity). |
context |
object or false |
Extra page context sent with chat. By default the SDK adds the current page URL and title. Pass an object to merge static fields over that, or false to send no context. |
token |
string | Preview Bearer token. Only for the in-app preview; do not use on your public site. |
#Mount options (chat & search)
On mount, the SDK loads config from the server, then merges your overrides. Dashboard settings are the defaults; anything you pass in the second argument wins except branding (plan-controlled).
| Option | Type | Description |
|---|---|---|
target |
CSS selector | Where to place the UI. Required for inline chat and for search when you want the SDK-rendered box. Optional mount point for bubble/sidebar. |
trigger |
CSS selector | Use your own element(s) instead of the default launcher or search box. See variants below. |
variant |
string | Override the shape from the dashboard. Chat: bubble | sidebar | inline. Search: modal | inline. |
side |
string | Chat sidebar only: right (default) or left. |
width |
number | Chat sidebar only: panel width in px (clamped 280–720, default 380). |
content |
CSS selector | Chat sidebar only: element that receives padding when open (default: document.documentElement). |
show_images |
boolean | Chat: show source images in answers as thumbnails (click to expand). Default off; set in the dashboard under Advanced. |
show_result_thumbnails |
boolean | Search: thumbnail on the right of each result. Chat: card under the answer for each cited page that has one. Default off; set on Look. |
theme |
string | auto | light | dark |
accent_light |
hex string | Accent colour in light mode (for example #18181b) |
accent_dark |
hex string | Accent colour in dark mode |
placeholder |
string | Input placeholder |
greeting |
string | Chat only: first assistant message |
prompts |
string[] |
Chat only: suggested questions shown as chips while the thread is empty (max 6). Click sends the question. Override from the SDK or set in the dashboard. |
avatar |
string | Chat only: image URL for the assistant |
summary |
boolean | Search only: AI summary above results (uses answer credits) |
speech_input |
boolean | Microphone button where the browser supports it |
debounce |
number | Search only: ms to wait after typing before searching (default 1000) |
default_language |
string | Fallback UI language when the browser language is unsupported |
#Suggested questions (chat)
On an empty conversation the chat can show chips under the greeting. Visitors click a chip to send that question immediately.
Set them in the dashboard (Suggested questions: type and press Enter, like domains), or pass prompts when mounting:
Datalumo.chat('ORG_ID/WIDGET_ID', {
variant: 'inline',
target: '#help',
prompts: [
'How does pricing work?',
'What is a refund?',
'How do I get started?',
],
}).mount();
Limits: up to 6 prompts, 100 characters each. Chips hide after the first message and return on New conversation.
#Chat variants
Bubble (default) — floating launcher opens a panel. No target needed.
Datalumo.chat('ORG_ID/WIDGET_ID').mount();
Custom launcher — hide the default bubble and open from your own control(s). Any matching element toggles the panel; you can also call open() / close() / toggle() on the handle:
<button id="open-help">Ask us</button>
<script src="https://datalumo.app/widget/v1/datalumo.js"></script>
<script>
const chat = Datalumo.chat('ORG_ID/WIDGET_ID', {
trigger: '#open-help', // or '.open-chat' for several buttons
}).mount();
// Optional: open from your own code without a click binding
// chat.open();
</script>
The default floating bubble is not rendered when trigger is set. Use a CSS selector that exists when .mount() runs.
Sidebar — full-height panel docked to the side. When open, the SDK pads the page (<html> by default) so content is pushed aside rather than covered. Uses the same floating bubble launcher as the bubble variant (or your own trigger).
Datalumo.chat('ORG_ID/WIDGET_ID', {
variant: 'sidebar',
// side: 'left', // default: 'right'
// width: 400, // px, default 380
// content: 'main', // pad this element instead of <html>
// trigger: '#help', // optional host launcher
}).mount();
No host markup is required. On viewports ≤ 640px the panel goes full-bleed and page padding is skipped. Prefer content when only a main column should shift (for example a flex layout with a fixed header).
Inline — panel fills a container (set a height on the container):
<div id="help" style="height: 560px;"></div>
<script>
Datalumo.chat('ORG_ID/WIDGET_ID', {
variant: 'inline',
target: '#help',
}).mount();
</script>
#Search variants
Modal (default) — Cmd/Ctrl+K opens a full search overlay. Optionally render a search-box trigger into target, or bind your own trigger.
// Keyboard only (Cmd/Ctrl+K)
Datalumo.search('ORG_ID/WIDGET_ID').mount();
// Trigger box inside #search
Datalumo.search('ORG_ID/WIDGET_ID', { target: '#search' }).mount();
// Your own button(s) open the modal
Datalumo.search('ORG_ID/WIDGET_ID', { trigger: '.open-search' }).mount();
Inline — input with results listed underneath.
// SDK renders the input into #search
Datalumo.search('ORG_ID/WIDGET_ID', {
variant: 'inline',
target: '#search',
}).mount();
// Use an existing <input>; results appear after it (or in target)
Datalumo.search('ORG_ID/WIDGET_ID', {
variant: 'inline',
trigger: '#my-search-input',
target: '#results', // optional
}).mount();
#Widget methods
After mount(), chat and search handles support:
| Method | Applies to | Description |
|---|---|---|
mount() |
both | Start loading config and render. Safe to call once; returns the same handle. |
destroy() |
both | Remove the widget from the page. |
open() |
both | Open the bubble panel or search modal. |
prefill(text) |
chat | Open the panel and put plain text in the composer. Does not send. Caps at 280 characters. First-party docs use this for ?ask=; customer embeds should not honour that query. |
ask(text) |
chat | Open the panel and send a message (search "Ask assistant" handoff). |
close() |
both | Close the panel or modal. |
toggle() |
chat | Open if closed, close if open. |
on('action', handler) |
chat | Listen when the visitor confirms a page action. handler receives the same detail as datalumo:action. |
off('action', handler) |
chat | Remove a page-action listener. |
Imperative methods queue until the widget is ready, so you can call them right after mount().
#Headless client
For a fully custom UI:
const dl = Datalumo.headless('ORG_ID/WIDGET_ID', {
user: { id: userId, hash }, // optional, signed
context: { product: 'billing' }, // optional
});
// Search
const hits = await dl.search('refund policy', { limit: 8 });
// Chat (full reply)
const reply = await dl.chat('How do refunds work?');
// reply: { text, conversationId, answerId, sources, actions }
// Chat (stream tokens)
await dl.chat('How do refunds work?', {
conversationId: reply.conversationId,
onToken: (delta, full) => { /* update UI */ },
onStatus: (status) => { /* searching | web_searching | sending | offering */ },
});
// Optional AI summary of search results
const summary = await dl.summarize('refund policy', {
onToken: (delta, full) => {},
});
// summary: { summarized, text }
// Analytics (fire-and-forget)
dl.trackEvent('click', { url: 'https://…', rank: 0, source: 'result' });
// Render markdown safely
container.innerHTML = Datalumo.markdown(reply.text);
Useful properties and helpers on the client:
| Member | Description |
|---|---|
sessionId |
Analytics session for this tab (auto-created and reused) |
identify(user) |
Set or clear { id, hash } after login |
fetchConfig() |
Presentation config JSON from the server |
listConversations() |
History for a signed visitor |
getConversation(id) |
One transcript |
keepConversation(id, kept?) |
Exempt from expiry (kept: false releases) |
deleteConversation(id) |
Delete one conversation |
deleteConversations() |
Delete all for this visitor |
When answer credits run out, chat returns 402 with plain search hits in data. The official chat widget renders those results; a headless client should do the same from the 402 body (no need to call search separately).
#Signed visitor identity
To restore chat history for a logged-in user, or to list/keep/delete conversations, prove the user id on your server with the widget's identity signing secret (dashboard → widget → For developers):
$hash = hash_hmac('sha256', (string) $userId, $signingSecret);
Pass the result into the SDK (never ship the signing secret to the browser):
// At init (preferred so every message is tagged)
const chat = Datalumo.chat('ORG_ID/WIDGET_ID', {
user: { id: String(userId), hash },
}).mount();
// Or after login
const headless = Datalumo.headless('ORG_ID/WIDGET_ID');
headless.identify({ id: String(userId), hash });
await headless.listConversations();
A bad hash is rejected; Datalumo will not treat a failed proof as an anonymous visitor.
See Authentication and Conversations.
#Analytics beacon (server-rendered results)
If you search on your server and render your own result list, load the lightweight analytics script so clicks still join the same funnel. Reuse the session_id your server received from search.
<script
src="https://datalumo.app/widget/v1/datalumo-analytics.js"
data-widget="ORG_ID/WIDGET_ID"
data-session="SESSION_FROM_SERVER"
></script>
<a href="https://example.com/docs/refunds"
data-dl-click
data-dl-rank="0"
data-dl-source="result">
Refunds
</a>
| Attribute | Required | Description |
|---|---|---|
data-widget |
yes | Widget key {organisation}/{widget} |
data-session |
no | Session id from the search response; omit to mint a new tab session |
data-base |
no | API origin override (defaults to the script origin) |
data-dl-click |
on links | Marks a result link for auto click tracking |
data-dl-url |
no | Override URL (defaults to href) |
data-dl-rank |
no | Result position |
data-dl-source |
no | e.g. result, summary |
Or initialise manually:
const analytics = DatalumoAnalytics.init({
key: 'ORG_ID/WIDGET_ID',
sessionId: '…',
});
analytics.trackClick({ url: 'https://…', rank: 0, source: 'result' });
Your domain must still be on the widget's website list.
#Presentation config
On mount the SDK requests:
GET /api/v1/{organisation}/widgets/{widget}/config
Typical payload:
{
"type": "chatbot",
"name": "Help",
"variant": "bubble",
"accent_light": "#18181b",
"accent_dark": "#fafafa",
"theme": "auto",
"branding": true,
"summary": false,
"speech_input": false,
"placeholder": "",
"greeting": "",
"prompts": ["How does pricing work?"],
"avatar": null,
"debounce": 1000,
"default_language": "en"
}
Responses may be cached for up to five minutes. branding follows your plan; clients cannot turn it off by override.
Most appearance and behaviour options are edited in the dashboard (shape, colours, greeting, suggested questions, AI summary, voice input, default language). Use SDK overrides for per-page differences (target, trigger, prompts, one-off theme).
#Languages
UI chrome (placeholders, buttons, status text) follows the visitor's browser language when supported, otherwise the widget's default_language, otherwise English.
| Locale | Language |
|---|---|
en |
English |
nl |
Dutch, Nederlands |
de |
German, Deutsch |
fr |
French, Français |
es |
Spanish, Español |
it |
Italian, Italiano |
pt-BR |
Portuguese (Brazil), Brazilian Portuguese, Português |
zh-Hans |
Chinese (Simplified), Mandarin, China, Singapore |
zh-Hant |
Chinese (Traditional), Taiwan |
zh-HK |
Chinese (Hong Kong), Cantonese, Hong Kong, Macau |
ja |
Japanese, 日本語 |
ko |
Korean, 한국어 |
th |
Thai, ไทย |
Browser tags such as zh-CN and zh-SG use Simplified / Mandarin. zh-TW uses Traditional. zh-HK and zh-MO use the Hong Kong / Cantonese locale. pt and pt-PT use Brazilian Portuguese.
#FAQ
What languages does Datalumo support?
Search and chat are multilingual. They work in most languages, even when the question and the source pages are not the same language. An English query can still find the right passages in a Cantonese knowledge base. Chat replies in the language of the question.
The table above is only the widget chrome: placeholders, buttons, status text. Those follow the visitor's browser when we have that locale. You can pin a language in the widget settings if you do not want that. Anything else falls back to English.
#Troubleshooting
| Symptom | Check |
|---|---|
| Widget does not load / config fails | Domain is on the widget's website list; key is {org}/{widget} public ids |
Console: no element matches target / trigger |
Selector exists when .mount() runs |
| Chat says wrong widget type | Use a chatbot widget with Datalumo.chat, not a search widget |
| Conversation history empty | Pass signed user.id + user.hash; signing secret must match |
| No analytics in the dashboard | Domain allowlist; wait for first search or chat; for custom results use the analytics beacon |
| Branding still shows | Controlled by plan, not by client override |
#Related
- Chat and Search for dashboard settings
- Widget API for the HTTP endpoints the SDK uses
- Authentication for domains, secret keys, and signed identity
- Errors & limits for status codes and rate limits