# Widget builder reference

> Code reference for KickBot widget builder widgets: every Kick event name and payload, globalValues, userVariables, chat helpers and how the preview runs code.

Source: https://kickbot.com/docs/widgets/widget-builder-reference

This page lists what your code can use in a KickBot [widget builder](https://kickbot.com/docs/widgets/widget-builder) widget: the `widgetEvent` events, the `globalValues` object, the `userVariables` object, and the `window.kickbotWidget` helpers. Every widget is plain HTML, CSS and JavaScript that runs as a browser source in OBS. KickBot connects to Kick for you and sends events to your code.

## How a widget runs

When OBS opens the widget URL, KickBot:

1. Loads the widget page with a transparent background.
2. Adds your CSS in a `<style>` element and puts your HTML in the page.
3. Loads your channel data from Kick (follower count, viewer count, stream status and recent chat) into `globalValues`.
4. Runs your JavaScript. It runs once, after step 3, so `globalValues` is ready when your code starts.
5. Sends live events to your code as `widgetEvent` events on `document`.

Your JavaScript can read two objects without importing anything: `globalValues` and `userVariables`. The helpers are on `window.kickbotWidget`.

Limits:

| Item | Limit |
| --- | --- |
| Width and height | Set in **Basic settings**. A new blank widget is 800 × 600. The New widget popup accepts up to 7680 × 4320. |
| HTML, CSS and JavaScript | Up to 2,000,000 characters per file |
| Widget name | Up to 500 characters |
| Files for user variables | Up to 25 MB per file |
| Builder widgets on the Free plan | 10 (no limit on Premium) |

## Listen to events

Add one listener for `widgetEvent` on `document`. Each event has `event.detail` with three fields:

| Field | Type | Value |
| --- | --- | --- |
| `event_name` | string | The event, for example `followEvent` |
| `source` | string | Always `kick` |
| `data` | object | The event payload (see below) |

```javascript
document.addEventListener('widgetEvent', (event) => {
    const { event_name, data } = event.detail;

    if (event_name === 'followEvent') {
        showAlert(data.user.username + ' followed!');
    }
    if (event_name === 'subEvent') {
        showAlert(data.username + ' subscribed for ' + data.months + ' months');
    }
});
```

### Event list

| Event name | When it fires | Test button |
| --- | --- | --- |
| `subEvent` | A viewer subscribes | Sub |
| `subGiftEvent` | A viewer gifts subs | Gifted sub |
| `kicksEvent` | A viewer sends KICKs | KICKs |
| `followEvent` | A viewer follows | Follow |
| `unfollowEvent` | A viewer unfollows | Unfollow |
| `chatMessageEvent` | A chat message is sent | Message |
| `chatMessageDeletedEvent` | A moderator deletes a message | Message deleted |
| `chatMessagePinnedEvent` | A message is pinned | Message pin |
| `chatMessageUnpinnedEvent` | The pinned message is removed | Message unpin |
| `chatClearedEvent` | A moderator clears the chat | Chat cleared |
| `followerCountUpdatedEvent` | Kick sends a new follower count | Follower count updated |
| `subCountUpdatedEvent` | The sub count on Kick changed | Sub count updated |
| `viewerCountUpdatedEvent` | Kick sends a new viewer count | Viewer count updated |
| `raidEvent` | Another channel raids (hosts) you | Raid |
| `streamOnline` | Your stream starts | Stream online |
| `streamOffline` | Your stream ends | Stream offline |

There is no tip event. For tip alerts, use the [tip alert widget](https://kickbot.com/docs/tipping/tip-alerts).

### Event payloads

These are the payloads in `event.detail.data`. They match the test events in the editor. Real events can have more fields. Names, numbers and IDs here are examples.

`subEvent`

```javascript
{
    months: 1,
    username: 'viewer_one',
    chatroom_id: 3847194
}
```

`subGiftEvent`

```javascript
{
    chatroom_id: 3847194,
    gifted_usernames: ['viewer_one', 'viewer_two', 'viewer_three'],
    gifter_username: 'big_gifter'
}
```

`kicksEvent`

```javascript
{
    message: 'Great stream!',
    sender: { id: 12345678, username: 'viewer_one', username_color: '#93EBE0' },
    gift: {
        gift_id: 'full_send',
        name: 'Full Send',
        amount: 100,
        type: 'BASIC',
        tier: 'BASIC',
        character_limit: 0,
        pinned_time: 0
    },
    channel: 'channel_3984507'
}
```

`followEvent` and `unfollowEvent` (same shape; `followers_count` is the new count)

```javascript
{
    id: 'd4e5f6a7-89b0-12c3-d456-78901e2f3a4b',
    channel: { id: 3984507, slug: 'kickbotdev' },
    user: { id: 14873539, slug: 'viewer_one', username: 'viewer_one' },
    followers_count: 36,
    created_at: '2024-05-13T22:19:07+00:00'
}
```

`chatMessageEvent`

```javascript
{
    id: '85c884b3-7377-478c-b6aa-22b7db752b93',
    chatroom_id: 3974112,
    content: 'hello [emote:37226:KEKW]',
    type: 'message',
    created_at: '2024-05-13T22:29:52+00:00',
    sender: {
        id: 4063326,
        username: 'viewer_one',
        slug: 'viewer_one',
        identity: {
            color: '#72ACED',
            badges: [{ type: 'subscriber', text: 'Subscriber', count: 3 }]
        }
    }
}
```

Kick emotes are in `content` as `[emote:ID:NAME]`. Use `createChatMessageFragment` (see [Show chat text safely](#show-chat-text-safely)) to turn them into images.

`chatMessageDeletedEvent`

```javascript
{
    id: '9cd3788a-ecd2-4051-a162-a7ea9be8ce7a',
    message: { id: '89d6be86-63db-4581-922e-14a6c4a9c2e6' }
}
```

Use `data.message.id` to find and remove the message you showed.

`chatMessagePinnedEvent` (`data.message` has the same shape as a `chatMessageEvent` payload)

```javascript
{
    message: {
        id: '5d798a38-ad17-45e4-8169-bda2faa85b79',
        chatroom_id: 3974112,
        content: 'This is a pinned message!',
        type: 'message',
        created_at: '2024-05-13T22:30:41+00:00',
        sender: { id: 4063326, username: 'kickbotdev', slug: 'kickbotdev', identity: { color: '#72ACED', badges: [] } }
    }
}
```

`chatMessageUnpinnedEvent`: `data` is an empty array (`[]`).

`chatClearedEvent`

```javascript
{ id: '47e3596f-0274-46e0-85b1-029fa3bfc5ea' }
```

`followerCountUpdatedEvent` (note the camelCase `followersCount`)

```javascript
{ followersCount: 1532, channel_id: 4599, created_at: 1715639984 }
```

`subCountUpdatedEvent`

```javascript
{ subscriber_count: 184, channel_id: 4599 }
```

`viewerCountUpdatedEvent`

```javascript
{ viewer_count: 320, channel_id: 5937485 }
```

`raidEvent`

```javascript
{
    chatroom_id: 839485,
    optional_message: 'gg stream',
    number_viewers: 150,
    host_username: 'raiding_channel'
}
```

`streamOnline`

```javascript
{
    livestream: {
        id: 3984507,
        channel_id: 3984507,
        session_title: 'Test stream!',
        created_at: '2024-05-13T22:30:41+00:00'
    }
}
```

`streamOffline`

```javascript
{
    livestream: {
        id: 3984507,
        channel: { id: 3984507, is_banned: false }
    }
}
```

## Global values

`globalValues` holds the current state of your channel. KickBot fills it before your code runs and keeps it up to date as events come in. Use it to draw your widget at the start, before any event arrives.

```javascript
globalValues = {
    subCount: 184,
    followerCount: 1532,
    viewerCount: 320,
    lastFollower: { /* the last follow */ },
    lastSub: { /* the last sub, for example { username, months } */ },
    lastGiftedSub: { /* the last gift, for example { gifter_username, gifted_usernames } */ },
    chatMessages: [ /* chatMessageEvent payloads */ ],
    streamerInfo: {
        kick: {
            username: 'KickBotDev',
            slug: 'kickbotdev',
            profile_picture: 'https://files.kick.com/images/user/.../fullsize.webp',
            user_id: '4063326',
            channel_id: '3984507',
            chatroom_id: '3974112',
            bio: 'Channel bio',
            subscriber_badges: [ /* your channel's sub badges from Kick */ ]
        }
    },
    currentStream: {
        is_live: true,
        title: 'Stream title',
        started_at: '2026-10-24 18:00:00'
    }
}
```

| Value | How it updates |
| --- | --- |
| `subCount` | Loaded from Kick. Goes up by 1 for each `subEvent` and by 1 for each gifted sub, and is set again by `subCountUpdatedEvent`. |
| `followerCount` | Loaded from Kick. Set by `followEvent`, `unfollowEvent` and `followerCountUpdatedEvent`. |
| `viewerCount` | Loaded from Kick (0 when offline). Set by `viewerCountUpdatedEvent`. |
| `lastFollower`, `lastSub`, `lastGiftedSub` | The last one that KickBot saw on your channel, or `null`. Updated by the matching event. |
| `chatMessages` | Recent chat from Kick when the widget loads, then each new message. Keeps the newest 100. `chatClearedEvent` empties it. To hide deleted messages, handle `chatMessageDeletedEvent` in your own code. |
| `streamerInfo.kick` | Your Kick profile. Does not change while the widget runs. |
| `currentStream` | Loaded from Kick when the widget loads. It is not updated by `streamOnline` or `streamOffline`. Listen to those events if you need the live state. |

## User variables

User variables are the settings that streamers see when they open your widget in Saved widgets. You add them under **User variables** in the editor. In your code, read the value with `userVariables.<variable name>.value`.

```javascript
const goal = Number(userVariables.follower_goal.value);
document.body.style.color = userVariables.text_color.value;
```

Fields of a variable:

| Field | What it is |
| --- | --- |
| Title | The label streamers see. |
| Variable name | The name in your code. Letters, numbers and `_` only. You cannot change it after you create the variable. |
| Tooltip | Extra help text under the title. |
| Variable type | See the table below. |
| Default value | The value before the streamer changes it. It can be empty. |
| Required | Streamers must enter a value before they can save. |

| Type | What the streamer sees | `value` in your code |
| --- | --- | --- |
| Text | A text box | A string |
| Number | A number box | A number or a numeric string. Use `Number()` to be safe. |
| Color | A color picker | A hex color string, for example `#ffffff` |
| Dropdown | A menu with your options | The chosen option as a string. The default is the first option. |
| Toggle | An on/off switch | `true` or `false` |
| Image/GIF | A URL box and an upload button | A URL to the file |
| Video | A URL box and an upload button | A URL to the file |
| Audio | A URL box and an upload button | A URL to the file |

Uploaded files are stored by KickBot, and `value` is the file URL. Use the menu next to a variable to move it up or down, edit it or delete it. The order in the editor is the order streamers see.

## Show chat text safely

Treat every event field and user variable as text from a stranger. KickBot cleans chat and event text before your code gets it, so it is safe as HTML text, but not as a URL, CSS or script. Do not put raw event fields into `innerHTML`. Use these helpers:

| Helper | What it does |
| --- | --- |
| `window.kickbotWidget.createChatMessageFragment(content, font_size)` | Returns a `DocumentFragment` with text nodes and Kick emote images. Emotes are `font_size + 3` pixels. `font_size` defaults to 20. |
| `window.kickbotWidget.createTextNode(value)` | Returns a text node for any value. |
| `window.kickbotWidget.escapeHtmlText(value)` | Escapes text for use between HTML tags when you must build an HTML string. Do not use it for URLs, CSS or script. |

```javascript
document.addEventListener('widgetEvent', (event) => {
    if (event.detail.event_name !== 'chatMessageEvent') return;
    const data = event.detail.data;

    const line = document.createElement('div');
    line.id = 'msg-' + data.id;
    const name = document.createElement('strong');
    name.style.color = data.sender.identity.color;
    name.append(window.kickbotWidget.createTextNode(data.sender.username + ': '));
    line.append(name);
    line.append(window.kickbotWidget.createChatMessageFragment(data.content, 20));
    document.getElementById('chat').append(line);
});
```

For other text, use `textContent` or `createTextNode`.

## The preview

The preview in the editor works like OBS, with a few differences for testing:

- When you change the code or a user variable, the preview runs your JavaScript again. KickBot removes the `widgetEvent` listeners from the last run first. Timers from `setInterval` and `setTimeout` are not removed, so click the reload button if they pile up.
- When you change the width or height, the preview reloads.
- Test events only go to the preview. A test **Follow** adds 1 to `globalValues.followerCount`, and **Unfollow** takes 1 away. **Viewer count updated** sends a sample number. **Message deleted** removes a random message from the preview chat.
- If your channel has no last follower, sub or gifted sub yet, the preview uses sample data for `lastFollower`, `lastSub` and `lastGiftedSub`.
- To see `console.log` output and errors, click the pop-out button and open the developer tools in that window.

## Example: follower goal

This is a short follower goal. It uses an event listener, `globalValues` and two user variables: `follower_goal` (Number) and `bar_color` (Color).

HTML:

```html
<div class="goal">
    <span id="count"></span> / <span id="target"></span>
    <div class="bar"><div class="fill"></div></div>
</div>
```

CSS:

```css
body { margin: 0; font-family: Arial, sans-serif; color: #fff; }
.goal { padding: 12px; background: #100c28; border-radius: 16px; font-size: 32px; font-weight: bold; }
.bar { height: 24px; margin-top: 8px; background: #3d3957; border-radius: 12px; overflow: hidden; }
.fill { height: 100%; width: 0; transition: width 0.3s ease; }
```

JavaScript:

```javascript
const target = Number(userVariables.follower_goal.value) || 100;
document.getElementById('target').textContent = target;
document.querySelector('.fill').style.background = userVariables.bar_color.value;

function update(count) {
    document.getElementById('count').textContent = count;
    document.querySelector('.fill').style.width = Math.min((count / target) * 100, 100) + '%';
}

document.addEventListener('widgetEvent', (event) => {
    const { event_name, data } = event.detail;
    if (event_name === 'followEvent' || event_name === 'unfollowEvent') update(data.followers_count);
    if (event_name === 'followerCountUpdatedEvent') update(data.followersCount);
});

update(globalValues.followerCount);
```

For a full version with more settings, start a new widget from the **Simple Follower Goal** template.

## Tips

- Draw the first state from `globalValues`, then change it on events. A widget that waits for an event looks empty until something happens.
- Saved widgets only shows test buttons for event names that appear in your JavaScript. Write the full event name (for example `'followEvent'`) in your code so streamers can test your widget.
- Keep the background transparent unless a background is part of the design, so the widget sits on top of the scene.
- You can also manage builder widgets with the KickBot API. See the [widgets API reference](https://kickbot.com/docs/api/reference/widgets).
