API reference
Exports, instance methods (ask, open, state, ready, reset, …), the global API, no-script triggers, deep links, JSX typings.
Last updated
Compact lookup for the SDK's exported functions, types, and events. For explanations + examples, see the Configuration and Events & hooks guides.
Exports#
import {
// Functions
init, // Mount (or re-config) a widget
mount, // Mount a new widget (non-idempotent)
destroy, // Unmount one or all widgets
hook, // Register a global lifecycle hook
getInstance, // Look up a live instance
getAllInstances, // Get every live instance
// Constants
version, // Semver string
brand, // { id, displayName, cssPrefix, cdnUrl, ... }
HOOK_WILDCARD, // "*" — wildcard for hook() arguments
// Types
type ChatWidgetOptions,
type ChatWidgetInstance,
type ResolvedOptions,
type EventMap,
type EventName,
type HookListener,
type LogEntry,
type LogLevel,
} from '@helpai/elements';Web component sub-entry (auto-registers the custom element):
import '@helpai/elements/web-component';React kit (React 18+; everything above plus the component and the hook, so import it all from this one path):
import {
HelpAI, // <HelpAI {...options} /> mounts the widget, re-configures it on prop changes, removes it on unmount
useHelpAI, // useHelpAI(widgetId?) → { state, ask, open, close, reset, track }, re-rendering on state changes
useHelpAITool, // useHelpAITool(tool) gives the agent a page tool while the component is mounted
type HelpAIProps,
type HelpAIControls,
} from '@helpai/elements/react';See Frameworks → React.
Functions#
init(opts): ChatWidgetInstance#
Mount a widget (or re-config an existing one).
function init(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;Idempotent — if an instance with the same widgetId exists, its
options are merged (via update) and the existing handle is
returned.
mount(opts): ChatWidgetInstance#
Force a fresh mount. If an instance exists at the same widgetId,
it's destroyed first.
function mount(opts: ChatWidgetOptions & { target?: HTMLElement }): ChatWidgetInstance;Prefer init() — safer for SPA route changes.
destroy(widgetId?)#
Unmount one or all widgets.
function destroy(widgetId?: string): void;- With
widgetId— destroys that one. - Without — destroys every widget on the page.
hook(widgetId, event, listener): () => void#
Register a global lifecycle hook. Fires for any widget matching
widgetId + event. Wildcards ("*") match anything.
function hook(widgetId: string, event: string, listener: HookListener): () => void;Returns an unsubscribe function.
getInstance(widgetId?): ChatWidgetInstance | null#
Look up a live instance by id.
function getInstance(widgetId?: string): ChatWidgetInstance | null;widgetId defaults to "default". Returns null if nothing is
mounted.
getAllInstances(): ChatWidgetInstance[]#
Returns every live widget instance on the page.
ChatWidgetInstance#
interface ChatWidgetInstance {
readonly widgetId: string;
// Panel control
open(target?: OpenTarget): void; // with a target: that tab, or one Help article / News post in it
close(): void;
expand(): void; // toggle normal ↔ expanded
openForm(id: string): void; // open the chat on that form
ask(text: string, options?: { send?: boolean }): void; // open the chat with `text` in the composer ({ send: true } sends it)
// State
readonly state: WidgetState; // { open, ready, unread, conversationId? }, live
readonly ready: Promise<void>; // resolves once connected
// Lifecycle
reset(): void; // start over as a new anonymous visitor (on logout)
login(userToken: UserTokenSource): void; // sign the visitor in as your logged-in user (a signed token, or a function returning one)
track(name: string, data?: Record<string, string | number | boolean>): void; // report your page's own event: analytics + the agent's next message
registerTool(tool: ModelContextTool, options?: { signal?: AbortSignal }): void; // a tool the agent can use on this page, for every widget on it
destroy(): void;
// Events
on<E extends EventName>(name: E, fn: (payload: EventMap[E]) => void): () => void; // returns unsubscribe fn
// Re-config
update(patch: Partial<ChatWidgetOptions>): void; // e.g. update({ tools }) on a route change
// Host-asserted context (informational, not auth; re-handshakes, same visitor/conversation)
setUserContext(ctx: Partial<NonNullable<ChatWidgetOptions['userContext']>>): void; // merges into the current user context
clearUserContext(): void;
setPageContext(ctx: Partial<NonNullable<ChatWidgetOptions['pageContext']>>): void; // merges into the current page context
clearPageContext(): void;
// Runtime diagnostics (module-global across widget instances)
setDebug(level: LogLevel | boolean | number | string): void;
readonly debugLevel: () => LogLevel;
onLog(sink: ((entry: LogEntry) => void) | null): void;
}ChatWidgetOptions#
The generated, schema-backed field reference is
docs/api/configuration.md; it is authoritative.
The public input keeps embed identity, context, endpoint overrides, storage,
and callbacks at the top level. Serializable widget settings use focused
nested sections. mount() and init() require aiAgentDeploymentId; the
deployment publicKey remains optional for intentional guest mode.
init({
publicKey: 'agent_pk_...',
aiAgentDeploymentId: '01900000-0000-7000-8000-000000000000',
presentation: {
mode: 'inline',
position: 'bottom-right',
iframe: false,
size: { width: '420px', height: '640px' },
},
behavior: {
startMinimized: false,
responseMode: 'streaming',
disableMessageFeedback: false,
autoSendSuggestions: false,
popOutUrl: 'https://chat.example.com',
},
theme: 'auto',
launcher: { variant: 'pill', label: 'Ask AI' },
header: { actions: ['theme', 'textSize', 'popOut', 'history', 'close'] },
composer: {
sendButton: { shape: 'circle', icon: 'arrowUp', variant: 'filled' },
attachments: { maxCount: 3, accept: 'image/*,application/pdf' },
},
feedback: { sound: { enabled: true }, haptics: { enabled: true } },
i18n: { locale: 'en', availableLocales: ['en', 'fr'] },
footer: {
composerDisclaimer: 'AI can make mistakes.',
poweredBy: { text: 'Powered by Help AI', href: 'https://help.ai' },
},
});The complete type also exposes features, forms, modules, and tracking.
See Configuration for behavior and examples.
ModelContextTool#
A tool the agent can use on your page, in the WebMCP tool shape (Page tools). On <web-ai-chat>
it's the tools property or registerTool(), since functions can't be attributes.
interface ModelContextTool<TInput = unknown> {
name: string; // letters, digits, `_` and `-` (58 at most; others become `_`); the agent sees `site__<name>`
title?: string;
description: string; // when to use it (1024 characters at most)
inputSchema?: Record<string, unknown>; // JSON Schema of an object; none = no input
annotations?: ToolAnnotations; // `consequentialHint: true` = the visitor approves each call first
execute(input: TInput, options: { signal: AbortSignal }): unknown; // a JSON result, or throw; `signal` aborts when the visitor moves on
}
interface ToolAnnotations {
readOnlyHint?: boolean;
untrustedContentHint?: boolean;
consequentialHint?: boolean;
debugging?: boolean;
}ActionName#
type ActionName = 'clear' | 'expand' | 'fullscreen' | 'popOut' | 'sound' | 'history' | 'language' | 'theme' | 'textSize' | 'close';FeedbackEvent#
type FeedbackEvent = 'messageReceived' | 'messageSent' | 'error' | 'voiceStart' | 'voiceStop';StringsOverride#
type LocaleStrings = Partial<Record<StringKey, string>>;
type StringsOverride =
| LocaleStrings // flat (applies to active locale)
| Partial<Record<string, LocaleStrings>>; // nested (per-locale)StringKey includes: attach, composerPlaceholder, send, stop,
close, fullscreen, expand, history, language, theme,
themeAuto, themeLight, themeDark, ... (full list in
src/i18n/strings.ts).
Storage adapter#
All adapter methods are synchronous.
type StorageAdapter = NonNullable<ChatWidgetOptions['storage']>;
interface StorageAdapterShape {
get(key: string): string | null;
set(key: string, value: string): void;
remove(key: string): void;
}open(target?)#
interface OpenTarget {
to: 'home' | 'chats' | 'help' | 'news';
id?: string; // a Help article or News post (its content id)
}Opens the panel; with a target it also shows that tab at its root, or the article / post in it. Back from the post
lands on the tab's list. A tab the widget doesn't have (or an unknown target) just opens the panel, with a warn log.
A target sent before the widget has connected waits until the deployment's tabs are known. For a site's own buttons:
document.querySelector('#whats-new')?.addEventListener('click', () => chat.open({ to: 'news' }));
document.querySelector('#help')?.addEventListener('click', () => chat.open({ to: 'help' }));
chat.open({ to: 'news', id: '019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01' }); // one postask(text, options?)#
ask(text: string, options?: { send?: boolean }): void;Opens the chat with text in the composer, focused, for the visitor to edit and send — the way a suggestion chip
does by default. With { send: true } it goes out right away as the visitor's message in their current conversation
instead. Use it for a site's own "Ask about pricing" buttons:
document.querySelector('#ask-pricing')?.addEventListener('click', () => chat.ask('How much does the Pro plan cost?'));
document.querySelector('#ask-now')?.addEventListener('click', () => chat.ask('Talk me through the Pro plan', { send: true }));The text waits until the chat can take it, so a button works whenever it's clicked: before the widget has connected
or while a pre-chat form is waiting for an answer; a send also waits for a reply still arriving. A newer ask
replaces one that is still waiting. Text is trimmed; an empty string is ignored with a warn log. If a send is
refused (for example the visitor is filling an inline form), the text is left in the composer for the visitor to send.
A send speaks for the visitor, so call it from something the visitor did (a click), not on page load.
Without script, mark any element on the page with data-helpai-ask; a click asks the default widget. An empty
value asks the element's own text, and data-helpai-send on the same element sends it instead of filling the composer:
<button data-helpai-ask="How do I book a session?">Book a session</button> <button data-helpai-ask data-helpai-send>Do you offer gift cards?</button>state and ready#
interface WidgetState {
open: boolean; // the panel is showing (always true for inline / page / standalone)
ready: boolean; // connected: commands act at once
unread: number; // conversations with replies the visitor hasn't read
conversationId?: string; // the visitor's current conversation
}chat.state is the widget's state right now; the state event delivers every change and unread fires when the
count changes. chat.ready resolves once the widget has connected, so code that needs the widget can await it
instead of listening for handshake. The unread count is known once the widget has been opened in the page load (a
closed widget makes no request beyond its handshake) and stays live from then on.
await chat.ready;
const badge = document.querySelector('#help-badge');
function showUnread(count: number) {
badge.textContent = String(count);
badge.hidden = count === 0;
}
showUnread(chat.state.unread);
chat.on('unread', showUnread);reset()#
Starts over as a new anonymous visitor. Call it when your user logs out on a shared computer: it forgets the stored
visitor, their current conversation (draft and queued messages included), preferences and the user context set with
setUserContext, then reconnects as a first visit. Listeners added with on() stay, and the handle keeps working.
On <web-ai-chat>, also remove a user-context attribute you set.
login(userToken)#
Signs the visitor in as your logged-in user. userToken is a JWT your server signs with the deployment's secret key,
or a function returning a fresh one (called again when the server rejects the current token as expired). The widget
reconnects as that user: their conversations on this browser move to them, and they reach their conversations on any
device where they're signed in. Call reset() when they sign out; switching to another user's token starts over the
same way. Also HelpAI('login', userToken), element.login(userToken) on <web-ai-chat> (not an attribute, so the
token stays out of the DOM), the userToken option at init(), and <HelpAI userToken={…} />. Signing and the
guarantees: Security → Verified users.
chat.login(() => fetch('/api/help-ai-token').then((res) => res.text()));registerTool(tool, options?)#
Gives the agent a tool on this page, for every widget on it: the latest registration of a name wins, and aborting
options.signal removes it. Also HelpAI('registerTool', tool) (queued before the script loads),
element.registerTool(tool) and the React kit's useHelpAITool(tool). Details: Page tools.
chat.registerTool({ name: 'open_booking', description: 'Open the booking form. Use it whenever the visitor wants to book a call.', execute: () => bookingForm.open() });track(name, data?)#
Reports something the visitor did on your site, outside the chat. It sends a hostEvent hit to Elements analytics
(not when tracking is off), and the widget keeps the last 10 for the agent, which reads them with the visitor's next
message. The name is 1–64 characters; the data is flat (strings, numbers, booleans; 256 characters of JSON at most).
Also HelpAI('track', name, data), element.track(name, data) and useHelpAI().track. Details:
Analytics → Your own events.
chat.track('plan_viewed', { plan: 'pro' });Events#
EventMap#
interface EventMap {
open: void;
close: void;
message: { role: string; text: string };
error: unknown;
expand: boolean; // new state
fullscreen: boolean; // new state
handshake: HandshakeResponse; // backend's bootstrap response
localeChange: string; // new BCP-47 tag
state: WidgetState; // open / ready / unread / conversation changed
unread: number; // the unread conversation count changed
// … and more: see `EventMap` in the package types
}
type EventName = keyof EventMap;HookListener#
Unified signature for global hooks:
type HookListener = (instance: ChatWidgetInstance, event: EventName, data: unknown) => void;Within a handler, narrow data by event name:
hook('*', '*', (inst, event, data) => {
switch (event) {
case 'message': {
const m = data as EventMap['message'];
break;
}
case 'expand':
case 'fullscreen': {
const flag = data as boolean;
break;
}
}
});Auto-boot via script tag#
The auto-boot parser exposes the documented flat data-* subset.
<script
src="https://cdn.help.ai/sdk/js/elements/latest/elements.js"
data-public-key="agent_pk_..."
data-ai-agent-deployment-id="01900000-0000-7000-8000-000000000000"
data-mode="floating"
data-theme="auto"
data-position="bottom-right"
data-launcher-variant="pill"
data-launcher-label="Ask us"
data-launcher-icon="spark"
data-response-mode="streaming"
data-features-disable-file-upload="true"
data-features-voice="local"
data-user-id="user_42"
data-user-name="Jane Doe"
data-page-area="support"
defer
></script>Full attribute list: Installation § script data attributes.
Leave out data-ai-agent-deployment-id to load the bundle without mounting, then call
HelpAI.init({ … }) from JavaScript (Installation § Load without mounting).
Global window.HelpAI API#
After the IIFE boots:
// Direct methods
window.HelpAI.init(opts)
window.HelpAI.mount(opts)
window.HelpAI.destroy(widgetId?)
window.HelpAI.getInstance(widgetId?)
window.HelpAI.getAllInstances()
window.HelpAI.hook(widgetId, event, fn)
// Shortcuts for the default widget
window.HelpAI.open() // or .open({ to: 'news', id? })
window.HelpAI.ask(text, { send }) // send defaults to false: fill the composer
await window.HelpAI.ready() // the default widget, once connected (also when it mounts later)
window.HelpAI.reset() // a new anonymous visitor (on logout)
window.HelpAI.close()
window.HelpAI.expand()
window.HelpAI.on(event, fn)
window.HelpAI.update(patch)
// Metadata
window.HelpAI.version // semver string
window.HelpAI.brand // { id, displayName, cssPrefix, cdnUrl, ... }
// Callable dispatch (for queued calls)
window.HelpAI('init', opts)
window.HelpAI('open')
window.HelpAI('open', { to: 'help' })
window.HelpAI('ask', 'How do I book?') // or ('ask', 'How do I book?', { send: true })
window.HelpAI('ready', (chat) => chat.on('unread', showUnread)) // queue-able before the bundle loads
window.HelpAI('reset')
window.HelpAI('hook', '*', 'message', fn)Pre-boot queues#
Used with the async loader snippet — write calls before the bundle arrives:
<script>
// Command queue
window.HelpAI =
window.HelpAI ||
function () {
(window.HelpAI.q = window.HelpAI.q || []).push(arguments);
};
// Hook queue — array of [widgetId, event, fn] tuples or { widgetId, event, listener } objects
window.HelpAIHooks = window.HelpAIHooks || [];
</script>The IIFE drains both on boot.
Web component#
Custom element tag name: <web-ai-chat>.
The Web Component exposes the same curated parser fields without the
script dataset's outer data- prefix:
<web-ai-chat
public-key="agent_pk_..."
ai-agent-deployment-id="..."
mode="inline"
theme="auto"
position="bottom-right"
launcher-variant="pill"
launcher-label="Ask us"
launcher-icon="chat"
actions="theme,history,close"
height="500px"
></web-ai-chat>Changing a reactive attribute re-resolves the live widget. iframe
and start-minimized are mount-time-only.
The element has the instance's commands, state and events, so a page drives it directly:
const chat = document.querySelector('web-ai-chat');
chat.open(); // or chat.open({ to: 'help' })
chat.ask('How do I book a session?');
chat.close();
chat.expand();
chat.openForm('lead');
await chat.ready;
chat.on('unread', (count) => console.log(count, chat.state));
chat.reset(); // on logouton() subscriptions end when the element leaves the page. Importing @helpai/elements/web-component types
document.querySelector('web-ai-chat') as WebAiChatElement in TypeScript; the
JSX typings do the same for React, Vue and Svelte markup.
No-script triggers#
Mark any element on the page and a click drives the default widget, with no script. A link keeps working without the widget; with it, the click opens the widget instead of following the link.
<a href="/help" data-helpai-open="help">Help</a>
<a href="/support/billing" data-helpai-open="help/019a0b6c-7e2a-7000-9d3e-4a5b6c7d8e01">Billing questions</a>
<button data-helpai-ask>Which plan fits a team of 10?</button>
<button data-helpai-form="book-demo">Book a demo</button>Deep links#
A page address can open the default widget on arrival — for support emails, docs and QR codes. Either parameter can sit in the query or the hash. The widget acts once and drops its parameters from the address (the page's own parameters and anchors stay), so a reload or a copied link doesn't repeat it. A linked question only fills the composer; it is never sent for the visitor.
JSX typings#
Types-only subpaths register <web-ai-chat> with its attributes in each framework's JSX, so attributes autocomplete,
ai-agent-deployment-id is required, and a ref or bind:this is the typed element:
import type {} from '@helpai/elements/jsx/react'; // React 19
import type {} from '@helpai/elements/jsx/svelte'; // Svelte 5, e.g. in src/app.d.ts
import type {} from '@helpai/elements/jsx/vue'; // Vue 3 (also set compilerOptions.isCustomElement for the tag)Brand constants#
window.HelpAI.brand (or import { brand } from '@helpai/elements'):
interface Brand {
id: string; // e.g. "help-ai"
name: string; // NPM package name, e.g. "@helpai/elements"
global: string; // window global, e.g. "HelpAI"
cssPrefix: string; // "helpai" — used for all CSS classes + custom properties
displayName: string; // "Help AI" — user-visible brand name
cdnUrl: string; // "https://cdn.help.ai/sdk/js/elements/latest/elements.js"
defaultBaseUrl: string; // "https://help.ai" — main origin; modules at /api/agent + /api/data
defaultTheme: {
accent: string;
accentText: string;
radius: string;
fontFamily: string;
};
}Read-only. Useful for:
- "Open in full Help AI dashboard" links.
- "Download the mobile app" banners.
- Dynamic theme fallbacks.
Versioning#
Semver. version export matches the NPM package.
- Patch (1.2.0 → 1.2.1): bug fixes, perf, docs. Auto-upgrade safe.
- Minor (1.2.0 → 1.3.0): new options, new events, backward-compatible. Auto-upgrade safe.
- Major (1.x.x → 2.0.0): breaking changes. Review the changelog.
Pin a specific version with the CDN URL:
<script src="https://cdn.help.ai/v1.2.0/elements.js" defer></script>Or use /v1/ for the latest 1.x.x (auto-patches, no major breaks).
See also#
- Configuration — options with examples + context.
- Events & hooks — event handling deep-dive.
- Recipes — copy-paste scenarios.
- Troubleshooting — when things go wrong.