> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heylua.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Widget configuration

> Every window.LuaPop.init() option with its default, what init() returns, and how admin dashboard settings, inline options, and the stored session ID combine

`window.LuaPop` is the global the hosted widget script defines. `init()` merges built-in defaults, the admin dashboard settings for the current domain, and the options you pass, then mounts the widget inside a shadow root on `document.body` (floating mode) or inside a container you name (embedded mode). The script is the only distribution; there is no npm package.

```html theme={null}
<script src="https://lua-ai-global.github.io/lua-pop/lua-pop.umd.js"></script>
```

## Quick example

```js theme={null}
const widget = await window.LuaPop.init({
  agentId: "agent_abc123",
  environment: "production",
  theme: "dark",
  sessionId: session.chatId, // unguessable, issued per end user by your backend
});

widget?.destroy();
```

## Methods

### init()

Mounts the widget and resolves once it is on the page.

```ts theme={null}
window.LuaPop.init(config?: Partial<LuaPopConfig>): Promise<WidgetInstance | null>
```

<ParamField path="config" type="Partial<LuaPopConfig>">
  The options listed under [Options](#options). Omit it to use admin dashboard settings alone.
</ParamField>

**Returns** — a `WidgetInstance` whose only method is `destroy()`, or `null` when nothing was mounted: no admin dashboard settings were found and no options were passed, the current path matches `excludedPaths`, or `displayMode` is `"embedded"` and the container is missing. Calling `init()` again replaces the mounted instance.

**Errors** — logged to the console, never thrown: `LuaPop: Invalid environment value "…"` (the widget then uses `"staging"`), `LuaPop: embeddedDisplayConfig.targetContainerId is required when displayMode is 'embedded'`, and `LuaPop: Target container with id "…" not found`.

### destroy()

Unmounts the widget and removes its host element. `window.LuaPop.destroy()` does the same for whichever instance is mounted and is safe to call when none is.

```ts theme={null}
widget.destroy(): void
window.LuaPop.destroy(): void
```

## Properties

### config

`window.LuaPop.config` is the merged configuration the mounted widget is using. Read it to see what the admin dashboard supplied.

## Options

Precedence, lowest to highest: built-in defaults, the admin dashboard settings for the domain (and `agentId`) the page loads on, and the object you pass to `init()`. Functions and regular expressions can only come from code.

### Agent and environment

Inside a tool, `Lua.request.channel` is `pop` for a widget turn, and `lua logs` shows `pop` in `metadata.channel`. The `Channel` type spells this value `web`, so compare against both `pop` and `web`; see [`Lua`](/reference/sdk/lua).

<ParamField path="agentId" type="string">
  The agent to talk to. Required unless the admin dashboard settings for the domain supply it; always pass it on a domain that hosts several agents.
</ParamField>

<ParamField path="environment" type="&#x22;production&#x22; | &#x22;custom&#x22; | &#x22;staging&#x22;" default="&#x22;staging&#x22;">
  Which platform the widget talks to. Use `"production"`. It is set for you only when admin dashboard settings are found, so pass it explicitly with any inline options. `"custom"` sends requests to `customBaseApiUri`. `"staging"` is Lua's internal platform, where your agent does not exist.
</ParamField>

<ParamField path="customBaseApiUri" type="string">
  API base URL used when `environment` is `"custom"`.
</ParamField>

<ParamField path="runtimeContext" type="string">
  Free text sent to the agent with every message, for example the current page or the end user's plan. Keep personal data out of it.
</ParamField>

### Identity

<ParamField path="sessionId" type="string">
  Identifies the end user's conversation and is a bearer secret: whoever presents it resumes that conversation. When omitted, the widget generates a UUID on first load and stores it in `localStorage` under `lua_pop_session_id`, so the same browser resumes the same conversation. A value you pass is stored the same way. Issue one unguessable value per signed-in end user from your backend, not a guessable one such as `customer-<id>`, and never log it or send it to analytics; every new value starts a new conversation. `destroy()` leaves the stored value in place, so on sign-out remove the key, then call `destroy()` and `init()` again. The widget end user is the current end user in `User.get()`; see [Identify users](/build/identify-users).
</ParamField>

<ParamField path="authToken" type="string">
  Replaces `sessionId` as the end user's identity on an agent that Lua has set up with a custom sign-in. On any other agent the token is ignored and, because the widget then sends no session ID, every page load starts a new anonymous conversation; leave it unset.
</ParamField>

### Display

<ParamField path="displayMode" type="&#x22;floating&#x22; | &#x22;embedded&#x22;" default="&#x22;floating&#x22;">
  `"floating"` shows a button that opens a chat window. `"embedded"` renders the conversation inside the container named in `embeddedDisplayConfig`: the chat is always open, and the floating-button and chat-window options have no effect.
</ParamField>

<ParamField path="embeddedDisplayConfig" type="object">
  Required when `displayMode` is `"embedded"`.

  <Expandable title="properties">
    <ParamField path="targetContainerId" type="string" required>
      `id` of the element to render into. The widget fills it when the element has an explicit height and otherwise uses `100dvh`.
    </ParamField>

    <ParamField path="conversationStarters" type="string[]">
      Prompts shown until the conversation has history. Clicking one sends it as a message.
    </ParamField>

    <ParamField path="useContainerHeight" type="boolean" default={false}>
      Always use `height: 100%`, for a container whose height changes.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="theme" type="&#x22;light&#x22; | &#x22;dark&#x22; | &#x22;auto&#x22;" default="&#x22;auto&#x22;">
  `"auto"` follows `prefers-color-scheme`; the other two pin the palette.
</ParamField>

<ParamField path="excludedPaths" type="(string | RegExp)[]">
  Paths where the widget must not appear. A string matches when `location.pathname` contains it; a `RegExp` is tested against the pathname. The widget also removes itself when a single-page app navigates to an excluded path and stays away until the next `init()`.
</ParamField>

### Floating button

<ParamField path="position" type="&#x22;bottom-right&#x22; | &#x22;bottom-left&#x22; | &#x22;top-right&#x22; | &#x22;top-left&#x22;" default="&#x22;bottom-right&#x22;">
  Corner of the viewport.
</ParamField>

<ParamField path="draggable" type="boolean" default={false}>
  Adds a grip so end users can drag the closed button elsewhere. `position` is the starting corner; the chosen spot is remembered per agent in the end user's browser.
</ParamField>

<ParamField path="buttonText" type="string" default="&#x22;Chat with us&#x22;">
  Label on the button. Emoji are fine here.
</ParamField>

<ParamField path="buttonColor" type="string">
  Background color of the button. Defaults to the theme's primary color.
</ParamField>

<ParamField path="buttonIcon" type="string">
  URL of an image rendered at 16 × 16 px in place of the Lua logo. Not an emoji.
</ParamField>

<ParamField path="popupButtonStyles" type="React.CSSProperties">
  Inline styles for the button: camelCase keys, plain values, no `!important`. A `background` here overrides `buttonColor`.
</ParamField>

<ParamField path="popupButtonPositionalContainerStyles" type="React.CSSProperties">
  Inline styles for the fixed container that holds the button and the chat window, for example `{ bottom: "24px", right: "24px" }`.
</ParamField>

### Chat window

<ParamField path="chatTitle" type="string" default="&#x22;Lua Assistant&#x22;">
  Title in the header.
</ParamField>

<ParamField path="chatTitleHeaderStyles" type="React.CSSProperties">
  Inline styles for the header.
</ParamField>

<ParamField path="chatHeaderSubtitle" type="object">
  The "Powered by" line under the title. Shown by default with the brand name `Lua`; clicking it opens a contact form.

  <Expandable title="properties">
    <ParamField path="visible" type="boolean" required>
      `false` hides the line.
    </ParamField>

    <ParamField path="brandName" type="string" default="&#x22;Lua&#x22;">
      Name shown after "Powered by".
    </ParamField>

    <ParamField path="iconUrl" type="string">
      Image shown before the name. Rendered only when `linkUrl` is also set.
    </ParamField>

    <ParamField path="linkUrl" type="string">
      Enables `iconUrl`.&#x20;
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="chatWindowHeight" type="string | number" default="&#x22;500px&#x22;">
  Height of the window on desktop, capped at 80% of the viewport height. Numbers are pixels. On screens narrower than 640 px, and on phones and tablets at any width, the window is full-screen instead.
</ParamField>

<ParamField path="chatWindowWidth" type="string | number" default="&#x22;350px&#x22;">
  Width of the window on desktop. Numbers are pixels. Ignored where the window is full-screen.
</ParamField>

<ParamField path="welcomeMessage" type="string" default="&#x22;👋 Welcome!&#x22;">
  First agent message when the conversation has no history. It is saved to the conversation, so the agent sees it as its own opening line.
</ParamField>

### Composer and features

<ParamField path="chatInputPlaceholder" type="string" default="&#x22;Ask anything, about anything&#x22;">
  Placeholder of the message box.
</ParamField>

<ParamField path="attachmentsEnabled" type="boolean" default={true}>
  Shows the attachment button. Accepted types: images, video, audio, `.pdf`, `.doc`, `.docx`, and `.txt`. Files upload to Lua's CDN before the message is sent.
</ParamField>

<ParamField path="microphoneEnabled" type="boolean" default={true}>
  Shows the microphone button, which records a voice message and sends it as audio together with any typed text.
</ParamField>

<ParamField path="voiceModeEnabled" type="boolean" default={true}>
  Shows the waveform button, which starts a live voice conversation. See [Voice chat in the widget](/channels/web-widget/voice-chat).
</ParamField>

<ParamField path="disablePreviewOnLinks" type="boolean" default={false}>
  Renders the URLs in a `::: links` block as plain links instead of fetching previews.
</ParamField>

### Navigation

<ParamField path="onNavigate" type="(pathname: string, options: { query: Record<string, string> }) => void">
  Called when a reply contains a [`::: navigate`](/channels/formatting/navigate) block, with the path and its parsed query parameters. The widget also reports to the platform that the page can navigate, but nothing adds the block to the prompt: include its syntax in the persona or a skill context. Code only.
</ParamField>

## Types

```ts theme={null}
interface WidgetInstance {
  destroy: () => void;
}
```

`LuaPopConfig` is the object type made of every option on this page. A declaration file to paste into a TypeScript project is on [Frameworks](/channels/web-widget/frameworks).

## See also

* [Add the chat widget to your website](/channels/web-widget/quickstart) — quickstart
* [Style the widget](/channels/web-widget/styling) — theme, colors, and CSS inside the shadow root
* [Track widget events](/channels/web-widget/events-and-analytics) — the `postMessage` events
* [Troubleshoot the widget](/channels/web-widget/troubleshooting) — symptom-first fixes
* [Channels](/concepts/channels) — where the widget fits among channels
