Widget builder reference
This page lists what your code can use in a KickBot 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
Section titled “How a widget runs”When OBS opens the widget URL, KickBot:
- Loads the widget page with a transparent background.
- Adds your CSS in a
<style>element and puts your HTML in the page. - Loads your channel data from Kick (follower count, viewer count, stream status and recent chat) into
globalValues. - Runs your JavaScript. It runs once, after step 3, so
globalValuesis ready when your code starts. - Sends live events to your code as
widgetEventevents ondocument.
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
Section titled “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) |
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
Section titled “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.
Event payloads
Section titled “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
{ months: 1, username: 'viewer_one', chatroom_id: 3847194}subGiftEvent
{ chatroom_id: 3847194, gifted_usernames: ['viewer_one', 'viewer_two', 'viewer_three'], gifter_username: 'big_gifter'}kicksEvent
{ 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)
{ 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
{ 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) to turn them into images.
chatMessageDeletedEvent
{ 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)
{ 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
{ id: '47e3596f-0274-46e0-85b1-029fa3bfc5ea' }followerCountUpdatedEvent (note the camelCase followersCount)
{ followersCount: 1532, channel_id: 4599, created_at: 1715639984 }subCountUpdatedEvent
{ subscriber_count: 184, channel_id: 4599 }viewerCountUpdatedEvent
{ viewer_count: 320, channel_id: 5937485 }raidEvent
{ chatroom_id: 839485, optional_message: 'gg stream', number_viewers: 150, host_username: 'raiding_channel'}streamOnline
{ livestream: { id: 3984507, channel_id: 3984507, session_title: 'Test stream!', created_at: '2024-05-13T22:30:41+00:00' }}streamOffline
{ livestream: { id: 3984507, channel: { id: 3984507, is_banned: false } }}Global values
Section titled “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.
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
Section titled “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.
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
Section titled “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. |
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
Section titled “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
widgetEventlisteners from the last run first. Timers fromsetIntervalandsetTimeoutare 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,lastSubandlastGiftedSub. - To see
console.logoutput and errors, click the pop-out button and open the developer tools in that window.
Example: follower goal
Section titled “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:
<div class="goal"> <span id="count"></span> / <span id="target"></span> <div class="bar"><div class="fill"></div></div></div>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:
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.
- 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.