Embed reference
Everything a developer needs to control how the Olotalk assistant loads and behaves.
Basic snippet
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id="YOUR_ASSISTANT_ID"
async
></script>
Place this before </body>. async is recommended so the loader stays non-blocking.
data-olotalk-* attributes
All configuration is passed as data- attributes on the script tag.
- 1
data-olotalk-assistant-idRequired: which assistant to load. - 2
data-olotalk-originOptional API origin override for self-hosting; omit it on Olotalk Cloud. - 3
data-olotalk-localeForce a language. Otherwise auto-detected. - 4
data-olotalk-cdn-baseWhere to fetch the bundle. Defaults to jsDelivr.
data-olotalk-assistant-id (required)
Your assistant’s unique identifier. Find it in the dashboard: open your assistant from Assistants — the ID is shown on its Install step (and in the page URL).
data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
data-olotalk-origin (optional)
The base URL of the Olotalk API. Omit it for Olotalk Cloud — the loader defaults to https://api.olotalk.com. Set this only in advanced self-managed deployments where the API runs on a different origin.
<!-- Self-managed deployment -->
data-olotalk-origin="https://api.example.com"
data-olotalk-locale
Force a specific locale for the assistant UI. Accepts any well-formed BCP 47 language tag — "en", "fr", "en-CA", "vi", "ja-JP", "sw", etc. UI strings for en/fr/de are bundled; other locales are translated by Olotalk on first request and cached.
When omitted, the assistant auto-detects from (in order): the visitor’s previously-accepted choice (localStorage) → the page’s <html lang> → navigator.language. The visitor’s auto-detected language is honoured only when it’s in the assistant’s supportedLocales set — operators declare which languages their site ships in the dashboard, and an unsupported visitor language falls through to the configured default. Visitors who type their first message in a different supported language see an inline Continue in {language}? prompt that swaps the chrome on accept.
data-olotalk-locale="fr"
See the Multilingual section in the loader README for the full priority chain.
This attribute is read once, when the assistant mounts. If your own
language switcher changes the page’s language without reloading it, call
Olotalk('locale', 'fr') — that swaps the assistant’s strings in place and
keeps the open conversation. See Driving the language
yourself.
data-olotalk-mode
Which shape to mount. Omit it and you get the corner bubble.
data-olotalk-mode="drawer"
Several shapes, comma-separated, share one conversation — the first is the residence:
data-olotalk-mode="bubble,drawer"
The full list, and what each one is for, is in Shapes.
Four of them — drawer, anchored, navfield and askbar — attach to
something on your page and need a line of JavaScript besides this attribute; the
rest work from the script tag alone.
An id the assistant does not recognise falls back to a working mount and says so in the console, so a typo degrades to a bubble rather than to a blank page.
data-olotalk-reach
Extra ways to summon the shape you mounted, comma-separated. selection opens
it beside a passage the visitor highlights, with that passage quoted above an
empty composer; commandk opens it on ⌘K.
data-olotalk-mode="bubble"
data-olotalk-reach="selection,commandk"
However many ways in you add, the page still has exactly one panel and one conversation — see Combining shapes. An id reach does not recognise is ignored with a console warning.
data-olotalk-open
Turns any element you already have into a door. No JavaScript.
<button data-olotalk-open>Ask AI</button>
<button data-olotalk-open="drawer">Ask AI</button>
<button data-olotalk-open data-olotalk-prefill="How does billing work?">Pricing help</button>
The value names which mounted shape to open; leave it empty when the page has
one panel, which is the usual case. data-olotalk-prefill opens it carrying a
question — the shape then sends it or leaves it editable according to its own
behaviour (a corner bubble sends; select-to-ask holds it so the visitor can
amend). A door naming a shape nothing has mounted warns in the console and does
nothing, rather than failing quietly.
A plain door toggles: press it again while the panel is open and it closes.
A door carrying data-olotalk-prefill always opens instead — those read as “ask
this”, where a second press means “ask it again”, not “never mind”.
This is what makes the slide-in drawer a paste: it has no launcher of its own by design, so its snippet ships this button with it. Move the button into your header, restyle it, or drop the attribute onto a button you already have.
data-olotalk-door="branded"
Opt a door into the assistant’s own look: a quiet chip at rest, with the launcher’s gradient rim turning around it on hover.
<button data-olotalk-open="drawer" data-olotalk-door="branded">Ask AI</button>
Opt-in on purpose. Without this attribute a door inherits your styles and we
touch nothing — the point of data-olotalk-open is that it goes on an element
you already have. The generated snippet includes the attribute, and the robot
mark inside its markup: the mark blinks, which a background image cannot do, so
it ships as inline SVG rather than being painted on. Delete the attribute at any
time to get your own button back.
data-olotalk-selection-answers
Where a highlight opens the panel. beside (the default) brings it to the
words, so the answer arrives where the question was asked. in-place opens it
where the assistant lives — worth choosing when your assistant is a fixture
visitors have already located and you would rather nothing moved.
data-olotalk-mode="bubble"
data-olotalk-reach="selection"
data-olotalk-selection-answers="in-place"
Only the selection trigger is affected; your own open({ anchor }) calls
position themselves as they always did. Any other value is ignored with a
console warning and you get beside.
data-olotalk-mount
CSS selector for the container the assistant renders into. Required for the
in-flow shapes — inline and expand — which live in your layout rather than
over it. Everything else positions itself against the viewport and ignores it.
data-olotalk-mount="#olotalk-assistant"
data-olotalk-sandbox-token
Preview feature. A signed, expiring token minted from the dashboard’s Test in your own app card. Its presence switches the assistant to the un-billed developer sandbox — see Testing in the sandbox for the full model. If the feature isn’t enabled for your workspace, ignore this attribute.
data-olotalk-sandbox-token="<token from the dashboard>"
data-olotalk-cdn-base
Override the CDN base URL where the assistant bundle and CSS are loaded from. Useful for self-hosting.
<!-- Self-hosted on your own origin -->
data-olotalk-cdn-base="https://assets.example.com/olotalk"
When omitted, the loader defaults to jsDelivr with an assistant version pin matching the loader build.
JSON attribute (alternative syntax)
Instead of individual data-olotalk-* attributes, you can pass a single JSON blob:
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk='{"assistantId":"ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy","locale":"fr"}'
async
></script>
The JSON attribute is merged with any individual data-olotalk-* attributes — individual attributes take precedence.
Global config object (advanced)
For full programmatic control, define window.OlotalkConfig before the loader script:
<script>
window.OlotalkConfig = {
assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
locale: 'fr',
theme: 'dark',
mode: 'inline', // which shape — 'bubble' is the default
mount: '#chat-container', // CSS selector — required for 'inline' and 'expand'
// origin: 'https://api.example.com', // only for self-managed deployments
// cdnBase: 'https://assets.example.com/olotalk', // only for self-hosted assets
// version: '1.2.3', // pin the assistant bundle version
};
</script>
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
async
></script>
window.OlotalkConfig is merged with data- attributes. data- attributes win on conflict.
Full config reference
| Field | Type | Default | Description |
|---|---|---|---|
assistantId | string | — | Assistant ID (required) |
siteId | string | — | Legacy alias for assistantId |
origin | string | https://api.olotalk.com (cloud build) | Olotalk API base URL. Omit for Olotalk Cloud; set only for self-managed deployments. |
locale | string | auto-detected | Explicit locale override (any BCP 47 tag). Auto-detect priority: visitor’s localStorage choice → <html lang> → navigator.language, gated by assistant’s supportedLocales. |
theme | "light" | "dark" | object | "light" | UI theme |
mode | shape id | "bubble" | Which shape to mount — see Shapes |
placement | "floating" | "embedded" | — | Deprecated — use mode. floating is bubble, embedded is inline. Still resolves, because it is in live snippets |
mount | string | document.body | CSS selector for the container. Required for inline and expand |
cdnBase | string | jsDelivr | Asset CDN base URL |
version | string | matching loader build | Assistant bundle version pin |
cssUrl | string | derived from cdnBase | Override assistant CSS URL |
Conversion config
The conversion pipeline enables intent-based CTA proposals and lead capture. When enabled, the assistant classifies each visitor message into a commercial intent category (pricing, demo, integration, support, general) and, when confidence is high enough, proposes a configured action alongside the answer.
Conversion config is server-side only. It lives in the assistant record — not in the client-side embed snippet — so you configure it from the dashboard, not the embed code.
Configure in the dashboard
Open your assistant from the Assistants nav and use the builder:
- CTA actions, confidence, and the webhook target live on the Base step, under Advanced configuration → Action policy. Pick a preset or go Custom, then set your booking URL, escalation email, webhook URL, and confidence threshold.
- Tool connections (Calendly booking, HubSpot) live on the Behavior step.
The recommended production confidence range is 0.7–0.8; lower it during testing to make CTAs trigger more easily.
CTA action types
| Action | Triggered by | What happens |
|---|---|---|
link | CTA button click | Opens href in a new tab |
lead_form | CTA button click | Inline form — email (required) + optional name/company/useCase fields |
escalate_to_human | CTA button click | Server-side action — notifies notifyEmail (configured server-side) |
trigger_webhook | CTA button click | Server-side action — POSTs lead data to the assistant’s webhookUrl |
book_meeting | CTA button click | Opens an inline Calendly scheduler (server-side connection) |
hubspot_create_contact_deal | CTA button click | Server-side action — creates a HubSpot contact + deal |
escalate_to_human targets (notifyEmail) and trigger_webhook targets (webhookUrl) are stored server-side and never exposed to the browser.
Page patterns and confidence tuning
By default, a CTA fires on any page. Restrict to specific paths with pagePatterns:
{ "pagePatterns": ["/pricing", "/integrations", "/docs"] }
Pattern matching is substring — /pricing matches https://example.com/pricing and https://example.com/pricing/annual.
Omit pagePatterns (or leave it empty) to match all pages.
minIntentConfidence (0–1) controls how clearly the visitor must express intent before the CTA fires. The recommended production range is 0.7–0.8. Lower to 0.5–0.6 during development to make triggering easier.
Lead scoring and webhook dispatch
When a lead is submitted via lead_form, Olotalk scores it based on intent, email domain, company presence, and use-case completeness. Leads that reach score ≥ 60 are marked qualified. If the assistant has a webhookUrl, Olotalk POSTs the lead payload to it — by default for every submitted lead (both new and qualified), not only qualified ones. See Leads & conversions for the full scoring reference and dispatch policy.
Conversion events
Listen to conversion outcomes on the assistant instance. These events are exposed on the @olotalk/assistant SDK instance returned by createAssistant() — the standard loader snippet manages the instance internally and does not expose them, so use the SDK path if you need to subscribe.
import { createAssistant } from '@olotalk/assistant'
const assistant = createAssistant(mountEl, { assistantId, origin })
assistant.on('cta_click', ({ id, action, intent }) => {
// id: CTA id from server config
// action: 'link' | 'lead_form' | 'escalate_to_human' | 'trigger_webhook'
// | 'book_meeting' | 'hubspot_create_contact_deal'
// intent: detected commercial intent that triggered the proposal
console.log('CTA clicked', id, action, intent)
})
assistant.on('lead_submit', ({ leadId, status, score }) => {
// status: 'new' | 'qualified'
// score: 0–100 lead quality score
console.log('Lead captured', leadId, status, score)
})
Embedded mode
By default the assistant renders as the corner bubble (a floating launcher in the bottom-right). To embed it inline inside an existing container:
<div id="chat-container" style="height: 600px;"></div>
<script>
window.OlotalkConfig = {
assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
mode: 'inline',
mount: '#chat-container',
};
</script>
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
async
></script>
The container must have an explicit height.
To make the embedded assistant match the surrounding page — font, colours, corners — set a few CSS variables. See Theming; a worked in-page example is at the bottom of that page.
Framework guides
Using a no-code builder or CMS? WordPress, Shopify, Wix, Squarespace, Webflow, Framer, Ghost, BigCommerce, Google Tag Manager and more each have a step-by-step guide under Install on your platform — with the exact menu path and platform-specific gotchas. The framework snippets below are for code-first apps.
The examples below assume Olotalk Cloud — data-olotalk-origin is omitted because the loader defaults to https://api.olotalk.com. In an advanced self-managed deployment, also pass data-olotalk-origin="https://api.example.com" (or set origin on window.OlotalkConfig).
Each guide below mounts the corner bubble — the default, right for most sites. Want it inline in your layout instead? See Choosing a surface for the current options, then Inline in a framework for the two-line change. To match your site’s look, see Theming.
Framework not listed? The integration is a single <script> tag, so the Plain HTML snippet works in any framework that renders markup — Solid, Qwik, Preact, Alpine, Rails, Django, Laravel, and the rest. For a component-native feel, the <olotalk-assistant> custom element works in any template.
React
import { useEffect } from 'react'
export function OlotalkAssistant() {
useEffect(() => {
if (document.getElementById('olotalk-loader')) return
window.OlotalkConfig = {
assistantId: process.env.NEXT_PUBLIC_OLOTALK_ASSISTANT_ID!,
}
const script = document.createElement('script')
script.id = 'olotalk-loader'
script.src =
'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
script.async = true
document.body.appendChild(script)
return () => {
window.Olotalk?.('destroy')
}
}, [])
return null
}
Add <OlotalkAssistant /> to your root layout (app/layout.tsx for Next.js App Router).
Next.js (Script component)
Requires Next.js 14+. The example below follows Next.js 16 conventions (async server components, no NEXT_PUBLIC_* env vars baked into the bundle).
Use data-* attributes directly on the <Script> tag. Read assistant config from plain (non-NEXT_PUBLIC_) env vars in an async server component so they are evaluated at request time, not baked in at build time.
// app/layout.tsx
import Script from 'next/script'
export const dynamic = 'force-dynamic' // read env vars at request time
export default async function RootLayout({ children }: { children: React.ReactNode }) {
const loaderSrc = process.env.OLOTALK_LOADER_SRC
return (
<html>
<body>
{children}
{loaderSrc && (
<Script
id="olotalk-loader"
src={loaderSrc}
strategy="afterInteractive"
data-olotalk-assistant-id={process.env.OLOTALK_ASSISTANT_ID}
/>
)}
</body>
</html>
)
}
Vue / Nuxt
// plugins/olotalk.client.ts
export default defineNuxtPlugin(() => {
window.OlotalkConfig = {
assistantId: useRuntimeConfig().public.olotalkAssistantId,
}
const script = document.createElement('script')
script.src =
'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
script.async = true
document.body.appendChild(script)
})
Astro
Add the loader to your shared layout. Astro bundles <script> tags by default, so mark it is:inline to keep the external loader and its data- attributes intact. Frontmatter expressions still evaluate at build/SSR time.
---
// src/layouts/Layout.astro
const assistantId = import.meta.env.PUBLIC_OLOTALK_ASSISTANT_ID
---
<!-- ...your layout... -->
<slot />
<script
is:inline
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id={assistantId}
async
></script>
Set PUBLIC_OLOTALK_ASSISTANT_ID in your .env (the PUBLIC_ prefix exposes it to the browser). For inline placement instead of the corner bubble, see Inline in a framework below.
Plain HTML / static sites
<!DOCTYPE html>
<html>
<body>
<!-- your page content -->
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
async
></script>
</body>
</html>
Remix
Add the loader in app/root.tsx after the <Scripts /> component. Use defer instead of async — Remix’s streaming renderer renders <Scripts /> last, so defer is more compatible.
// app/root.tsx
import { Scripts } from '@remix-run/react'
export default function App() {
return (
<html>
<body>
<Outlet />
<Scripts />
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id={process.env.OLOTALK_ASSISTANT_ID}
defer
/>
</body>
</html>
)
}
Environment variables in Remix are server-only by default. If you need them in a client component, expose them via a loader and pass them as props, or use .env with VITE_ prefix for Vite-based Remix projects.
SvelteKit
Option A — src/app.html (simplest, no env vars)
<!-- src/app.html -->
<body>
%sveltekit.body%
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
async
></script>
</body>
Option B — +layout.svelte with env var injection
<!-- src/routes/+layout.svelte -->
<script>
import { onMount } from 'svelte'
export let data
onMount(() => {
if (document.getElementById('olotalk-loader')) return
window.OlotalkConfig = {
assistantId: data.olotalkAssistantId,
}
const script = document.createElement('script')
script.id = 'olotalk-loader'
script.src =
'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
script.async = true
document.body.appendChild(script)
})
</script>
<slot />
// src/routes/+layout.server.ts
import { env } from '$env/static/private'
export function load() {
return {
olotalkAssistantId: env.OLOTALK_ASSISTANT_ID,
}
}
Angular
Option A — src/index.html (simplest)
<!-- src/index.html -->
<body>
<app-root></app-root>
<script
src="https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js"
data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy"
async
></script>
</body>
Option B — AppComponent with environment.ts
// src/app/app.component.ts
import { Component, OnInit } from '@angular/core'
import { environment } from '../environments/environment'
@Component({ selector: 'app-root', templateUrl: './app.component.html' })
export class AppComponent implements OnInit {
ngOnInit() {
if (document.getElementById('olotalk-loader')) return
;(window as any).OlotalkConfig = {
assistantId: environment.olotalkAssistantId,
}
const script = document.createElement('script')
script.id = 'olotalk-loader'
script.src =
'https://cdn.jsdelivr.net/npm/@olotalk/assistant-loader@0/dist/loader.iife.js'
script.async = true
document.body.appendChild(script)
}
}
// src/environments/environment.ts
export const environment = {
production: false,
olotalkAssistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
}
Inline in a framework
Every guide above mounts the corner bubble. To place the assistant inline — a block inside your own layout instead of a corner launcher — you have two options that work in any framework.
Simplest — the custom element. <olotalk-assistant> renders wherever you put it, so mode="inline" makes it inline in any HTML or component template. Register it once at startup, then drop the tag in a sized container:
// once, at app startup (main.ts / entry file)
import { defineOlotalkAssistantElement } from '@olotalk/assistant'
defineOlotalkAssistantElement()
<div style="height: 600px">
<olotalk-assistant assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy" mode="inline"></olotalk-assistant>
</div>
Imperative — mount into a ref. When you’d rather control mounting yourself, call createAssistant(element, …) with the container. Shown in React; Vue (ref + onMounted), Svelte (bind:this + onMount) and Angular (@ViewChild + ngAfterViewInit) follow the identical pattern — a container ref plus a mount-on-init, destroy-on-unmount lifecycle.
import { useEffect, useRef } from 'react'
import { createAssistant } from '@olotalk/assistant'
export function InlineAssistant() {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
if (!ref.current) return
const assistant = createAssistant(ref.current, {
assistantId: 'ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy',
mode: 'inline',
})
return () => { void assistant.destroy() }
}, [])
return <div ref={ref} style={{ height: 600 }} />
}
The container needs an explicit height — the assistant fills it. Both paths are documented in full under Assistant JS API. To make an inline assistant match the surrounding page, set a few CSS variables — see Theming.
Self-hosting
To host the assistant bundle on your own infrastructure:
- Install the npm packages on a machine with Node.js:
npm install @olotalk/assistant-loader @olotalk/assistant - Copy the dist files to your static asset server:
cp node_modules/@olotalk/assistant/dist/olotalk-assistant.js /your/assets/ cp node_modules/@olotalk/assistant/dist/olotalk-assistant.iife.js /your/assets/ cp node_modules/@olotalk/assistant/dist/olotalk-assistant.css /your/assets/ cp node_modules/@olotalk/assistant-loader/dist/loader.iife.js /your/assets/ cp node_modules/@olotalk/assistant/dist/version.json /your/assets/ - Set
data-olotalk-cdn-baseto the URL where your assets are served. Leavedata-olotalk-originoff so the loader keeps calling Olotalk Cloud’s API (only set it in an advanced self-managed deployment where the API runs on your own origin):<script src="https://assets.example.com/olotalk/loader.iife.js" data-olotalk-assistant-id="ast_oltk_7Kd2mQxRv9TbNhLpW3Zsy" data-olotalk-cdn-base="https://assets.example.com/olotalk" async ></script>
The loader requests {cdnBase}/olotalk-assistant.js on module-capable browsers, falls back to {cdnBase}/olotalk-assistant.iife.js when needed, and loads CSS from {cdnBase}/olotalk-assistant.css. It also requests {cdnBase}/version.json for cache-busting — omit that file and everything still works, but you’ll see a 404 in the console and visitors may hold a cached bundle longer.
Ingesting your content
Olotalk builds your assistant’s knowledge base by crawling the pages you point it at — all from the dashboard, no API calls or scripts required.
Open your assistant from the Assistants nav and go to the Source step. Two things stay independent here:
- Source URLs — what content gets indexed into the knowledge base
- Install domains — where the launcher is allowed to run (set on the Install step)
Add the URLs you want indexed, or enter your site root and let Olotalk discover pages from your sitemap.xml and robots.txt (it filters out disallowed paths automatically). Start the crawl, and the Ingestion status, coverage & languages panel on the same step tracks progress. When you update your content, re-crawl from the same panel — Olotalk remembers the discovered URL set, so you don’t have to re-enter it.
Security
Install domain allowlist — Olotalk validates every request against the assistant’s configured install domains. Add the hostname where your snippet runs on the assistant builder’s Install step, under Install domains (open your assistant from the Assistants nav). Requests from unlisted domains receive 403.
Rate limiting — the config endpoint is limited to 60 requests per IP per minute. The ask endpoint inherits the per-assistant rateLimitPer5Min setting (default: 20).
Request tracing — every response includes an x-request-id header. If you pass your own value in the request, Olotalk echoes it back unchanged; otherwise a UUID is generated. Include this header when reporting issues — it correlates your request to the server logs.
Content Security Policy — if your site uses a strict CSP, add:
script-src 'self' https://cdn.jsdelivr.net;
style-src 'self' https://cdn.jsdelivr.net;
connect-src 'self' https://api.olotalk.com https://cdn.jsdelivr.net;
Adjust cdn.jsdelivr.net if self-hosting. If you use the book_meeting CTA, also allow the scheduler iframe with frame-src https://calendly.com; (use the host your booking links point to).