Option reference
The table of every option, generated from the schemas; authoritative.
Last updated
The canonical surface lives in src/schema/widget.ts and
src/schema/deployment.ts as Zod schemas. Every field carries a
.describe(...) string and (where useful) .meta({ examples }) —
this page is a flat reference auto-generated from those annotations.
When the schema and this doc disagree, the schema wins. Run
bun run gen:docs after editing a schema to refresh the tables; CI
fails if this file is out of sync.
For longer-form prose on what each subsystem does, see the
features/ directory:
- Theming
- Sizing
- Launcher
- Header actions
- Composer
- Feedback
- i18n
- Modes
Conventions#
- Every field is optional unless its row says otherwise.
- "Default" is what the runtime applies when the field is absent.
—means there's no schema-level default; the resolver falls through to runtime behaviour or leaves the field undefined. - Field names use the schema (camelCase) form. The web-component +
<script data-*>boot accept the kebab-case equivalent (e.g.data-launcher-variant↔launcher.variant). - "Server-pushable" fields can arrive in
HandshakeResponse.config; see § Server settings.
<!-- AUTO-GENERATED:CONFIG-REFERENCE START -->
<!-- This block is generated by scripts/gen-config-docs.ts from the Zod
schemas in src/schema/widget.ts + src/schema/deployment.ts. Edit
those schemas (.describe(...) + .meta({ examples })) and run
bun run gen:docs to regenerate. CI fails when this block drifts. -->
Connection (ConnectionConfig)#
Where the widget talks to and how it authenticates. Owned by the embedding developer (typically baked into the page template / env var) — distinct from userPrefs, which is the dashboard-pushable UX surface.
Examples:
[
{
"publicKey": "agent_pk_demo",
"aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001"
},
{
"publicKey": "agent_pk_demo",
"aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
"widgetId": "support"
},
{
"publicKey": "agent_pk_demo",
"aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
"baseUrl": "https://api.acme.com"
}
]Deployment widget settings (WidgetSettings)#
Portal-configurable widget settings — thirteen sections, one per dashboard tab. The backend pushes this shape on /pai/handshake under config, except forms, which the data API serves separately.
Examples:
[
{
"presentation": {
"mode": "floating",
"position": "bottom-right"
},
"theme": "auto"
},
{
"presentation": {
"mode": "floating"
},
"theme": {
"accent": "#7c3aed",
"radius": "12px"
},
"launcher": {
"variant": "pill",
"label": "Ask AI",
"callout": {
"text": "👋 Need help?"
}
}
},
{
"presentation": {
"mode": "inline"
},
"behavior": {
"responseMode": "streaming"
},
"features": {
"voice": "local"
}
}
]Full widget config (WidgetConfig)#
Full tooling config — connection (where + auth) + widget settings (look + behaviour) + page-mode handshake extras. The production widget does not import this Zod schema. Mode-specific constraints (e.g. position only meaningful in floating) are NOT enforced here so the same object can flow across mode changes.
Examples:
[
{
"publicKey": "agent_pk_demo",
"aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
"presentation": {
"mode": "floating"
},
"theme": "auto"
},
{
"publicKey": "agent_pk_demo",
"aiAgentDeploymentId": "01900000-0000-7000-8000-000000000001",
"presentation": {
"mode": "modal",
"size": {
"width": "720px",
"height": "640px"
}
}
}
]Handshake response (HandshakeResponse)#
<!-- AUTO-GENERATED:CONFIG-REFERENCE END -->
Storage#
interface ClientStorage {
get(key: string): string | null;
set(key: string, value: string): void;
remove(key: string): void;
}Adapter methods are synchronous. Swap the default localStorage adapter for
sessionStorage, an encrypted wrapper, or an in-memory shim (tests, SSR). The
adapter receives dynamic draft.<conversationId> keys whose values are exact
unsent textarea text and owns their storage, retention, and privacy behavior.
storage and the function-typed onMessage / onOpen / onClose /
onError / target callbacks are runtime-only and not on the schema
(they can't be JSON-serialised).
Callbacks#
Short-hand for instance.on(...) — useful for one-off integrations
that don't want to keep a handle.
Resolved options (internal)#
After resolveOptions(raw), every field is either a default or a
user-supplied value. Internal modules always depend on the
ResolvedOptions shape. Key differences from the input:
themeMode(ThemePreference) andthemeOverrides(ThemeOverrides) are split from the singlethemeinput.launcher.calloutis either aRequired<...>object ornull(not undefined).poweredByis eitherPoweredByOptionornull.stringsis fully populatedRecord<StringKey, string>(no missing keys).stringsOverrideis preserved separately soAppcan re-resolve into a new locale at runtime.localeis a concrete BCP-47 tag ("auto"already resolved).
Full shape in src/core/config/types.ts ResolvedOptions.
Server settings#
ServerConfig (the handshake config field) is a subset of
ChatWidgetOptions. Server can push anything that ships under the
schemas above; callbacks (onMessage etc.) and identity fields
(publicKey) are deliberately excluded.
Merge policy (src/core/config/merge.ts mergeServerConfig):
- Scalars — user wins per-field. If the user didn't set it, server's value applies.
- Objects — leaf merge. Server provides defaults; any user-set leaf wins.
header.actions— replace-whole list; server and user arrays are never concatenated.i18n.strings— locale-aware merge; flat-form overrides apply to the active locale only, nested-form overrides apply per-locale.
Runtime safety#
The IIFE auto-boot bundle does NOT run Zod at runtime — pulling Zod
in would push the bundle ~70 KB gz over budget. Instead, every
field flows through resolveOptions in src/core/config/resolve.ts,
which uses ?? default fallbacks for every option. A bad value
(wrong enum, malformed length, etc.) flows through and the
downstream consumer either uses the value as-is or substitutes a
sane default — the widget always mounts.
For strict validation (the dashboard / dev configurator), import
schemas directly from the @helpai/elements/schema package subpath
and call .safeParse(). That path DOES include Zod; it's only
loaded in admin contexts, not on every customer page.
Pass footer: { poweredBy: { text: "" } } to opt out of the brand attribution.