---
title: "Widget SDK"
url: "/build/widget-sdk"
description: "Embed and control the KlicForge chat widget from JavaScript — installation, configuration, methods and events."
productArea: build
audience: ["developer"]
tags: ["widget", "sdk"]
lastReviewed: 2026-08-25
---

# Widget SDK (/build/widget-sdk)





The widget SDK embeds the KlicForge chat widget in a web page and gives you a JavaScript API to
control it.

The widget renders inside an isolated container, so its styles cannot leak into your page and
your page's styles cannot break it.

## Installation [#installation]

<Tabs items="['Script tag', 'npm']">
  <Tab value="Script tag">
    Add this before the closing `</body>` tag:

    ```html
    <script src="https://unpkg.com/@bymos/agentkit-sdk@0.8/agentkit-widget.iife.js"></script>
    <script>
      window.AgentKit.init({
        agentId: 'your-agent-id',
        tenantId: 'your-tenant-id',
        apiBaseUrl: 'https://api.klicforge.ai',
      });
    </script>
    ```

    This registers `window.AgentKit` synchronously.
  </Tab>

  <Tab value="npm">
    ```bash
    npm install @bymos/agentkit-sdk
    ```

    ```jsx
    import { useEffect } from 'react';
    import { init } from '@bymos/agentkit-sdk';

    export function ChatWidget() {
      useEffect(() => {
        const widget = init({
          agentId: 'your-agent-id',
          tenantId: 'your-tenant-id',
          apiBaseUrl: 'https://api.klicforge.ai',
        });

        return () => widget.destroy();
      }, []);

      return null;
    }
    ```

    Not using React? Call `init(config)` once, after your page has loaded, and keep the
    returned instance around to call `destroy()` yourself when you're done with it.
  </Tab>
</Tabs>

Find your agent ID and workspace ID in the dashboard, and get a ready-made copy-paste snippet on
the agent's **Sandbox** tab, or from an agent's **Channels → Widget** settings.

<Callout type="warn">
  The widget only loads on domains listed in the agent's allowed origins. If you see an
  access-restricted message, add the site's origin under the agent's **Channels → Widget** settings.
</Callout>

## Mounting [#mounting]

`init()` creates a floating widget with a launcher button. To embed the chat inside an element
of your own layout instead, use `mount()`:

```js
window.AgentKit.mount(document.getElementById('support-chat'), {
  agentId: 'your-agent-id',
  tenantId: 'your-tenant-id',
  apiBaseUrl: 'https://api.klicforge.ai',
});
```

## Configuration [#configuration]

`init()` and `mount()` accept these fields:

| Field                   | Type                      | Notes                                                                                                                                                     |
| ----------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentId`               | `string`                  | Required.                                                                                                                                                 |
| `apiBaseUrl`            | `string`                  | Required.                                                                                                                                                 |
| `tenantId`              | `string`                  | Tenant/organisation identifier. Required for conversation tracking and widget validation.                                                                 |
| `user`                  | `object`                  | Identity of the end-user — see [Identifying your users](#identifying-your-users).                                                                         |
| `sessionId`             | `string`                  | Resume a specific session instead of the one persisted in `localStorage`.                                                                                 |
| `mode`                  | `'floating' \| 'inline'`  | Default `'floating'`. Use `'inline'` with `mount()` to embed in an element of your layout.                                                                |
| `title`                 | `string`                  | Default `'AI Assistant'`. Overrides the agent's display name in the header.                                                                               |
| `subtitle`              | `string`                  | Short text shown below the agent name in the widget header.                                                                                               |
| `description`           | `string`                  | Used in the widget empty state. Defaults from the server; can be overridden here.                                                                         |
| `avatarUrl`             | `string`                  | URL of the avatar image shown in the header and launcher.                                                                                                 |
| `theme`                 | `object`                  | `mode` (`'light' \| 'dark' \| 'system'`), `accentColor`, `fontFamily`, `borderRadius`.                                                                    |
| `metadata`              | `Record<string, unknown>` | Arbitrary metadata attached to the session.                                                                                                               |
| `streaming`             | `boolean`                 | Default `true`. Set `false` to receive whole replies instead of token streams.                                                                            |
| `preview`               | `boolean`                 | Dashboard sandbox only. Sends the session cookie so a signed-in tenant member can chat with a draft/inactive agent. Leave `false` for third-party embeds. |
| `skipServerConfigFetch` | `boolean`                 | Dashboard sandbox only. Skips the SDK's own config fetch when the host already supplies display fields inline.                                            |
| `configChannel`         | `'draft' \| 'published'`  | Dashboard sandbox only. `'draft'` requires a session cookie for the owning tenant and is inert for third-party embeds.                                    |

## Controlling the widget [#controlling-the-widget]

Both `init()` and `mount()` return an instance:

```js
const widget = window.AgentKit.init({ ... });

widget.open();
widget.close();
widget.toggle();
widget.sendMessage('Hello');
widget.reset();          // start a fresh conversation
widget.getState();
widget.destroy();
```

`window.AgentKit.destroy(id)` removes a widget by ID, and calling it with no argument removes
all of them.

## Events [#events]

```js
widget.on('message:received', (event) => {
  console.log(event);
});

widget.off('message:received', handler);
```

| Event                                          | Fires when                       |
| ---------------------------------------------- | -------------------------------- |
| `ready`                                        | The widget has initialised       |
| `open` / `close`                               | The widget is opened or closed   |
| `conversation:started`                         | A new conversation begins        |
| `message:sent`                                 | The user sends a message         |
| `message:received`                             | The agent's reply arrives        |
| `message:error`                                | A message fails                  |
| `stream:start` / `stream:delta` / `stream:end` | Streaming reply lifecycle        |
| `control_mode_changed`                         | A human takes over or hands back |
| `reset`                                        | The conversation is reset        |
| `destroy`                                      | The widget is torn down          |

## Identifying your users [#identifying-your-users]

If you already know who the visitor is, pass an external identifier so their conversations link
to the same [contact](/contacts) across sessions and devices.

```js
window.AgentKit.init({
  agentId: 'your-agent-id',
  tenantId: 'your-tenant-id',
  apiBaseUrl: 'https://api.klicforge.ai',
  user: {
    externalId: 'user_123', // your own DB user ID, UUID, etc.
    authId: 'auth0|abc123', // ID from your auth system — highest-priority lookup key
    name: 'Jane Doe',
    email: 'jane@example.com',
  },
});
```

All `user` fields are optional — omit `user` entirely for anonymous sessions.

<Callout type="warn">
  Do not pass personal data you would not want in a browser. The identifier should be an opaque ID
  from your own system, not an email address.
</Callout>

## Appearance [#appearance]

Greeting, branding, colours, whether file upload and voice notes are enabled, and links to your
privacy policy and terms are configured on the agent's **Widget** tab, so non-developers can
change them without a deployment. `title`, `avatarUrl`, and `theme` in the init config override
those defaults for a specific embed when set — see [Configuration](#configuration).

## Sessions [#sessions]

A conversation persists across page loads in the browser, so a visitor who navigates around your
site keeps their conversation. `reset()` starts a fresh one. Sessions expire after a period of
inactivity set on the agent.

From 0.8 the SDK also establishes an abuse-protection session when the visitor opens the chat
panel. It needs no configuration, adds nothing to a page whose chat is never opened, and a
failure to establish one never blocks a message.

## Troubleshooting [#troubleshooting]

| Symptom                        | Likely cause                                                      |
| ------------------------------ | ----------------------------------------------------------------- |
| `window.AgentKit is undefined` | The script tag runs after your init code, or failed to load       |
| Access-restricted message      | The origin is not in the agent's allowed origins                  |
| Widget opens but never replies | The agent's status is `draft` or `inactive`                       |
| Widget is clipped or invisible | A parent element has `overflow: hidden` or a low stacking context |
| Uploads rejected               | The file type or size is not supported                            |

## Related pages [#related-pages]

* [Web widget](/channels/web-widget)
* [Developer overview](/build)
* [Contacts](/contacts)
