# Option reference

> The table of every option, generated from the schemas; authoritative.

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](#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.

| Field                 | Type                                                                                | Default     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------- | ----------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `widgetId`            | `string`                                                                            | `"default"` | Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide.                                                                                                                                                                                                                                                                             |
| `publicKey?`          | `string`                                                                            | —           | Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present.                                                                                                                                                                                                                                                                                                                 |
| `aiAgentDeploymentId` | `string`                                                                            | —           | Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`.                                                                                                                                                                                                                                                                                                          |
| `baseUrl?`            | `string`                                                                            | —           | MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time.                                                                                                                                                                  |
| `agentApiBaseUrl?`    | `string`                                                                            | —           | Full agent-API base — set it to reach a STANDALONE `module-agent-api` directly (local module on its own port, e.g. `http://localhost:3069`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable.                                                                                                                                                         |
| `dataApiBaseUrl?`     | `string`                                                                            | —           | Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3071`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`).                                                                                         |
| `userContext?`        | object                                                                              | —           | Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent.                                                                                                                                                                                                                                        |
| `pageContext?`        | object                                                                              | —           | Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent.                                                                                                                                                                                                                                                                    |
| `endpoints?`          | object                                                                              | —           | HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too.                                                                                                                                                                                                                                                 |
| `debug?`              | `boolean` \| `"off"` \| `"error"` \| `"warn"` \| `"info"` \| `"debug"` \| `"trace"` | —           | Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value. |

**Examples:**

```json
[
  {
    "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.

| Field           | Type                                                    | Default  | Description                                                                                                                                                                                                                                                            |
| --------------- | ------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `presentation?` | object                                                  | —        | Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset.                                                                                                                                                                                |
| `behavior?`     | object                                                  | —        | Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).                                                                                                                                                                          |
| `theme`         | `"auto"` \| `"light"` \| `"dark"` \| `"host"` \| object | `"auto"` | Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`).                                                                                                                                                                   |
| `launcher?`     | object                                                  | —        | Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only.                                                                                                                             |
| `header?`       | object                                                  | —        | Panel-header configuration.                                                                                                                                                                                                                                            |
| `composer?`     | object                                                  | —        | Composer chrome — send-button styling + attachment limits.                                                                                                                                                                                                             |
| `feedback?`     | object                                                  | —        | Audible + tactile feedback.                                                                                                                                                                                                                                            |
| `i18n?`         | object                                                  | —        | Locale + i18n overrides.                                                                                                                                                                                                                                               |
| `footer?`       | object                                                  | —        | Footer — attribution + composer disclaimer.                                                                                                                                                                                                                            |
| `features?`     | object                                                  | —        | Capability flags — file upload, voice mode, HITL, and operator requests.                                                                                                                                                                                               |
| `forms?`        | object[]                                                | —        | Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), shows in the conversation (a `blocking` one locks the message box until submit/skip), dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals. |
| `modules?`      | object[]                                                | —        | Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + the content collections it shows (`contentCollectionIds`) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only.       |
| `tracking?`     | object                                                  | —        | Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId.                    |

**Examples:**

```json
[
  {
    "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.

| Field                 | Type                                                                                | Default     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| --------------------- | ----------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `widgetId`            | `string`                                                                            | `"default"` | Stable per-instance id used to namespace localStorage keys. Set this when mounting more than one widget on the same page so their conversation state doesn't collide.                                                                                                                                                                                                                                                                             |
| `publicKey?`          | `string`                                                                            | —           | Deployment public key (`agent_pk_…`) issued from the dashboard. Sent on every API call as the `x-public-key` header when present.                                                                                                                                                                                                                                                                                                                 |
| `aiAgentDeploymentId` | `string`                                                                            | —           | Required deployment UUID from the dashboard. Picks which agent / settings bundle answers this mount. Sent as `x-ai-agent-deployment-id`.                                                                                                                                                                                                                                                                                                          |
| `baseUrl?`            | `string`                                                                            | —           | MAIN site origin (e.g. `https://help.ai`) — the API modules derive from it: agent API at `${baseUrl}/api/agent`, data API at `${baseUrl}/api/data`. Set this to point at your staging / self-hosted / custom backend. Default: the brand's `defaultBaseUrl` baked at build time.                                                                                                                                                                  |
| `agentApiBaseUrl?`    | `string`                                                                            | —           | Full agent-API base — set it to reach a STANDALONE `module-agent-api` directly (local module on its own port, e.g. `http://localhost:3069`; routes attach as-is: `/pai/handshake`). Precedence: this option > derived from the main origin (`${baseUrl}/api/agent`). Not server-pushable.                                                                                                                                                         |
| `dataApiBaseUrl?`     | `string`                                                                            | —           | Full data-API base (e.g. `https://help.ai/api/data`, or a standalone local module like `http://localhost:3071`) — the service serving content, form definitions (`GET /pai/bootstrap`), and form submissions at root-level paths. Precedence: this option > server-pushed `config.dataApiBaseUrl` > derived from the main origin (`${baseUrl}/api/data`).                                                                                         |
| `userContext?`        | object                                                                              | —           | Host-asserted end-user context (informational, untrusted — not authentication). Recognised `id` / `email` / `name` / `plan` / `role` / `phone` plus any extra custom scalar fields forwarded to the agent.                                                                                                                                                                                                                                        |
| `pageContext?`        | object                                                                              | —           | Host-asserted page/visit context (informational, untrusted). Where the visitor is + the surface (`area`) they're using, plus any custom scalar fields, forwarded to the agent.                                                                                                                                                                                                                                                                    |
| `endpoints?`          | object                                                                              | —           | HTTP endpoint overrides. `upload` / `transcribe` paths are appended to the agent base (`${baseUrl}/api/agent`), `submitForm` to the data base (`dataApiBaseUrl`); absolute URLs are accepted too.                                                                                                                                                                                                                                                 |
| `debug?`              | `boolean` \| `"off"` \| `"error"` \| `"warn"` \| `"info"` \| `"debug"` \| `"trace"` | —           | Console log verbosity. `false` / `"off"` silences everything (default in production). `true` → `"debug"`. Levels `error` / `warn` / `info` / `debug` / `trace` gate output additively. Use `"trace"` to capture per-chunk SSE frames + every state transition. Also toggleable at runtime via `chat.setDebug(level)` or `?<brand>-debug=trace` in the page URL — the URL beats this option, this option beats the persisted `localStorage` value. |
| `presentation?`       | object                                                                              | —           | Layout fundamentals — mode, anchor corner, iframe isolation, panel size + drawer inset.                                                                                                                                                                                                                                                                                                                                                           |
| `behavior?`           | object                                                                              | —           | Conversation behaviour — lifecycle, streaming, and the transcript surface (scroll, feedback).                                                                                                                                                                                                                                                                                                                                                     |
| `theme`               | `"auto"` \| `"light"` \| `"dark"` \| `"host"` \| object                             | `"auto"`    | Theme preference (`auto` follows OS) or a tokens override object (`accent`, `radius`, `fontFamily`).                                                                                                                                                                                                                                                                                                                                              |
| `launcher?`           | object                                                                              | —           | Floating-launcher (FAB) appearance and behaviour. The FAB is the round button in the corner that opens the chat panel. Floating mode only.                                                                                                                                                                                                                                                                                                        |
| `header?`             | object                                                                              | —           | Panel-header configuration.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `composer?`           | object                                                                              | —           | Composer chrome — send-button styling + attachment limits.                                                                                                                                                                                                                                                                                                                                                                                        |
| `feedback?`           | object                                                                              | —           | Audible + tactile feedback.                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `i18n?`               | object                                                                              | —           | Locale + i18n overrides.                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `footer?`             | object                                                                              | —           | Footer — attribution + composer disclaimer.                                                                                                                                                                                                                                                                                                                                                                                                       |
| `features?`           | object                                                                              | —           | Capability flags — file upload, voice mode, HITL, and operator requests.                                                                                                                                                                                                                                                                                                                                                                          |
| `forms?`              | object[]                                                                            | —           | Event-driven forms — an ordered list (order = priority). Each binds to trigger event(s), shows in the conversation (a `blocking` one locks the message box until submit/skip), dedupes by frequency, and records via `endpoints.submitForm`. Generic across verticals.                                                                                                                                                                            |
| `modules?`            | object[]                                                                            | —           | Messenger tabs — an ordered list of up to 4 tabs, each picking a `layout` + the content collections it shows (`contentCollectionIds`) + a translatable `label`. One tab → no tab bar (just that content); zero → an empty state. Floating / drawer / modal only.                                                                                                                                                                                  |
| `tracking?`           | object                                                                              | —           | Anonymous usage tracking for the widget owner — GA-style pixel events (open, send, form submit, …) with page / device / locale dimensions. Never includes message or form content; the only visitor identifier is the widget's anonymous visitorId.                                                                                                                                                                                               |
| `site?`               | —                                                                                   | —           | Site config — applies in `mode: "page"`. Comes from the handshake response, not user-edited.                                                                                                                                                                                                                                                                                                                                                      |
| `blocks?`             | —                                                                                   | —           | Blocks (nav + link cards) — applies in `mode: "page"`. Comes from the handshake response.                                                                                                                                                                                                                                                                                                                                                         |

**Examples:**

```json
[
  {
    "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`)

| Field            | Type     | Default | Description |
| ---------------- | -------- | ------- | ----------- |
| `deployment`     | object   | —       |             |
| `agent`          | object   | —       |             |
| `config?`        | object   | —       |             |
| `site?`          | object   | —       |             |
| `blocks?`        | object   | —       |             |
| `welcome?`       | object   | —       |             |
| `visitorId`      | `string` | —       |             |
| `conversationId` | `string` | —       |             |
| `userPrefs?`     | object   | —       |             |
| `rebind?`        | object   | —       |             |

<!-- AUTO-GENERATED:CONFIG-REFERENCE END -->

---

## Storage

| Option    | Type            | Default                 | Server-push |
| --------- | --------------- | ----------------------- | ----------- |
| `storage` | `ClientStorage` | `LocalStorageAdapter()` | no          |

```ts
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

| Option      | Type                            |
| ----------- | ------------------------------- |
| `onMessage` | `(msg: { role, text }) => void` |
| `onOpen`    | `() => void`                    |
| `onClose`   | `() => void`                    |
| `onError`   | `(err: unknown) => void`        |

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) and `themeOverrides` (ThemeOverrides)
  are **split** from the single `theme` input.
- `launcher.callout` is either a `Required<...>` object or `null`
  (not undefined).
- `poweredBy` is either `PoweredByOption` or `null`.
- `strings` is fully populated `Record<StringKey, string>` (no
  missing keys).
- `stringsOverride` is preserved separately so `App` can re-resolve
  into a new locale at runtime.
- `locale` is 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.
