TokElementsDOCS
WIDGET SDK / V1
01

Getting started

1
Write markup & style

The HTML & CSS tabs draw your widget. The box is transparent and floats over the video — never paint a full opaque background.

2
React to events

In the JS tab, subscribe with TE.on('gift', fn). window.TE is already loaded — no imports, no setup.

3
Test & add

Use Simulate or the Fire buttons to preview offline, then Add to overlay — or let Claude build it via MCP.

Your first widget — a gift shout-out· edit the JS, the preview reacts live
var box = document.getElementById('box');
TE.on('gift', function (ev) {
  box.textContent = ev.user.name + ' sent ' + ev.gift.name + '!';
  box.classList.remove('pop'); void box.offsetWidth; box.classList.add('pop');
});
Live preview
02

Connect Claude (MCP)

Build and edit these widgets straight from Claude Desktop or Claude Code. Generate a token, add the connector, and Claude can call these tools on your account:

get_docsThe full authoring guide + every trigger and action (Claude reads this first).
list_widgetsList your saved widgets with their ids.
get_widgetFetch one widget’s html / css / js.
create_widgetCreate a new HTML widget from name + html/css/js.
update_widgetEdit an existing widget.
delete_widgetDelete a widget by id.
list_templatesList the built-in templates to copy from.
get_templateRead a template’s html/css/js.
create_from_templateCopy a template into a new widget.
list_community_templatesBrowse widgets published by the community.
install_community_templateInstall a private, editable fork of a community template.
Loading…
03

The TE SDK

Everything a widget does goes through the global window.TE object.

TE.on(type, fn)

Subscribe to a live event. `type` is any trigger below; `fn(ev)` runs each time it happens.

TE.on('*', fn)

Catch every event. `fn(ev, type)` receives the payload and the event name.

TE.off(type, fn)

Remove a previously registered handler.

TE.onGift(name, fn)

Fire only for gifts whose name matches (case-insensitive substring). Omit `name` to catch every gift.

TE.onSticker(idOrUrl, fn)

Fire only for one specific subscriber emote / sticker — matched by its TikTok id, image URL, or source.

TE.rules.allowUser / allowGift

Reusable eligibility filters for roles, allow/deny lists, gift name and minimum coin value.

TE.metrics

The latest stream snapshot object (viewers, likes, coins, followers, topGifters…). Also delivered via TE.on('metrics', fn).

TE.defineSettings([...])

Declare streamer-tweakable controls; returns the current values (defaults merged with the streamer's choices).

TE.settings

The current settings values object (same shape defineSettings returned).

TE.on('settings', fn)

Runs when the streamer changes a setting live — re-render with the new values.

TE.state.get/set/increment

Promise-based atomic values for totals and shared game state.

TE.collection.*

Server-owned join-once participants and score tables: join, increment, list, count, remove and clear.

TE.queue.*

Bounded server-owned FIFO queues for media, requests and viewer-triggered actions.

TE.shared.*

Opt several widgets into one named channel-state namespace; unrelated widgets remain isolated.

TE.random.draw(name, opts)

Securely draw one or more winners from a collection and record the result.

TE.cooldown.claim(scope, user, ms)

Atomically claim a global or per-user cooldown; returns claimed and retryAfterMs.

TE.timer.*

Start, pause, reset and read a persisted wall-clock timer that survives reloads.

TE.points.trySpend(user, amount, reason)

Check and debit points in one transaction; always inspect result.ok before running a paid interaction.

04

Triggers

Live events you can listen to. Every user contains { id, name, username, avatar, roles }.

TE.on('reward')

A reward attempt completed. status is redeemed or insufficient; successful events are already charged.

{ status, user, reward: { id, name, cost, currency, trigger }, balance, missing, message|null }
TE.on('gift')

A viewer sent a gift. coins = total for the combo; streakEnd marks the combo finished.

{ user, gift: { name, image|null, coins, repeat, combo, streakEnd } }
TE.on('follow')

A viewer followed the account.

{ user }
TE.on('subscribe')

A viewer subscribed. months = how many months in a row.

{ user, months }
TE.on('share')

A viewer shared the live.

{ user }
TE.on('chat')

A chat message. emotes = inline subscriber-emote image URLs in this message.

{ user, comment, emotes:[url,] }
TE.on('like')

A viewer sent likes. count = this burst; total = running total.

{ user, count, total }
TE.on('join')

A viewer entered the room. isTop = a top-gifter joined.

{ user, isTop }
TE.on('sticker')

A subscriber emote or on-screen sticker sent live. url = the emote image; id = its TikTok id.

{ user|null, id|null, url, source }
TE.on('metrics')

The latest stream snapshot. TE.metrics holds it; the event fires whenever it updates.

{ live, viewers, likes, followers, coins, topGifters:[],}
TE.on('milestone')

A round-number milestone was crossed (likes, coins, followers, subs).

{ metric, value, label }
TE.on('poll')

TikTok's native live poll — real votes, start to finish.

{ state:'start'|'update'|'end', title, options:[{text,votes}], endsAt }
TE.on('battle')

LinkMic battle updates: battle cards, fan tickets, army sizes.

{ card|null, tickets|null, armies|null, battleId|null }
TE.on('rank')

Hourly-ranking position and rank-up moments.

{ rank|null, from|null, to|null, countdown|null }
TE.on('envelope')

A red envelope / treasure box dropped in the live.

{ }
TE.on('pinned')

The host pinned a comment.

{ text }
TE.on('deleted')

A moderator removed a chat message (id matches the earlier chat event).

{ id }
TE.on('streamState')

The stream went live or offline.

{ live }
TE.on('apiEvent')

A custom event posted through the Event API. Listen to apiEvent or directly to its custom name.

{ name, data }
TE.on('nowPlaying')

The streamer's current Spotify track PLUS the up-next queue. Fires on connect and whenever the song, play-state, progress or queue changes. playback is null when nothing is playing; queue is the upcoming tracks.

{ connected, playback: { isPlaying, progressMs, durationMs, title, artists:[], album, artwork|null } | null, queue:[{ title, artist, artwork|null, durationMs }] }
Only one specific gift or emote
TE.onGift('Rose', function (ev) { /* … */ });
TE.onSticker('<sticker id>', function (ev) { /* … */ });

On the Stickers page, hover any emote → Trigger auto-builds a widget wired to that exact emote.

05

Actions

What your widget can do in response — plain browser APIs, ready to paste.

Show an image

Pop an image on screen — the gift/emote artwork, an uploaded asset, or any URL. Auto-removes after a few seconds.

// Show an image, then fade it out
TE.on('gift', function (ev) {
  var img = document.createElement('img');
  img.src = ev.gift.image;                 // ← any image URL works
  img.style.cssText = 'position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);max-width:60%';
  document.body.appendChild(img);
  setTimeout(function () { img.remove(); }, 4000);   // ← how long it stays
});
Play a sound

Play audio on an event. Expose a sound setting so the streamer picks their own file — no code editing.

// Play a sound the streamer chose in settings
var s = TE.defineSettings([{ key: 'sfx', label: 'Alert sound', type: 'sound', default: '' }]);
TE.on('settings', function (ns) { s = ns; });
TE.on('gift', function (ev) {
  if (s.sfx) { var a = new Audio(s.sfx); a.volume = 0.8; a.play(); }
});
Show / animate text

Write dynamic text and re-trigger a CSS animation by toggling a class.

// Announce the event with a CSS pop animation
var box = document.getElementById('box');   // your element in the HTML tab
TE.on('follow', function (ev) {
  box.textContent = ev.user.name + ' followed!';
  box.classList.remove('pop'); void box.offsetWidth; box.classList.add('pop');
});
Show a live stat

Bind an on-screen number to a live metric — viewers, likes, coins, followers — updated automatically.

// Keep a number in sync with the live stream
TE.on('metrics', function (m) {
  document.getElementById('count').textContent = m.viewers.toLocaleString();
});
Read it aloud (TTS)

Speak an event with the browser’s text-to-speech. Great for gift or follow shout-outs.

// Text-to-speech shout-out
TE.on('gift', function (ev) {
  var u = new SpeechSynthesisUtterance(ev.user.name + ' sent ' + ev.gift.name);
  speechSynthesis.speak(u);
});
Remember things (TE.store)

Simple synchronous persistence for visual state. Use the transactional runtime for entrants, purchases and shared scores.

// Persistent state: survives OBS reloads
var total = TE.store.get('total', 0);          // read (with default)
TE.on('gift', function (ev) {
  total += ev.gift.coins;
  TE.store.set('total', total);                 // write (auto-saved)
  render();
});
React to chat commands

Exact-match chat commands plus atomic collections make safe entries, votes and spins possible.

// One server-authoritative entry per viewer
TE.onCommand('!join', function (c) {
  TE.collection.join('entrants', c.user.id, c.user).then(function (result) {
    if (result.joined) renderCount(result.count);
  });
});
Trigger stream actions (TE.act)

Request overlay alerts, sounds, TTS, counters, subathon time or OBS scene switches directly from a widget.

// One call, one stream action
TE.act({ id: 'tts', message: 'New high score!' });
TE.act({ id: 'obsScene', scene: 'Hype cam' });        // needs the Desktop Bridge
Award viewer points

Transactionally check, spend or award loyalty points and receive the resulting balance.

// Only spin after the points were really debited
TE.onCommand('!spin', function (ev) {
  TE.points.trySpend(ev.user, 50, 'Wheel spin').then(function (result) {
    if (result.ok) spin();
    else showMissing(result.missing);
  });
});
Build fair games

Atomic state, collections, secure random draws, cooldowns and persistent timers — safe across duplicate browser sources.

// Fair draw from a server-owned entrant collection
TE.random.draw('entrants', { count: 1 }).then(function (result) {
  if (result.ok) reveal(result.winners[0].value);
});
Add streamer controls

Expose text, colors, numbers, toggles — the streamer tweaks them live in the overlay editor, no code needed.

// No-code controls for whoever uses the widget
var s = TE.defineSettings([
  { key: 'title',  label: 'Title', type: 'text',  default: 'Goal' },
  { key: 'accent', label: 'Color', type: 'color', default: '#FF2E4D' },
]);
document.body.style.setProperty('--accent', s.accent);
TE.on('settings', function (ns) { s = ns; /* re-render with new values */ });
06

Streamer settings

Declare controls with TE.defineSettings([...]) and whoever uses the widget tweaks them live in the overlay editor — no code. Each field:

A placed widget with one or more 'button' fields exposes those actions in its settings inside the Overlay Editor. The Widget Builder displays the same buttons for safe preview testing while coding.

typerendersvalue
'text'Single-line text inputstring
'number'Number inputnumber
'color'Color pickerhex string, e.g. "#FF2E4D"
'select'Dropdown (needs options: [...])one of the options
'toggle'On/off switchboolean
'range'Slider (min / max / step)number
'button'Declarative action buttonincrement, set or toggle — validated JSON, never dashboard code
'sound'Picker over the streamer's uploaded sounds + Uploadthe chosen file URL ("" = none)
'image'Picker over the streamer's uploaded images + Uploadthe chosen file URL ("" = none)
var s = TE.defineSettings([
  { key: 'title',  label: 'Title', type: 'text',   default: 'Follower goal' },
  { key: 'target', label: 'Goal',  type: 'number', default: 100 },
  { key: 'accent', label: 'Color', type: 'color',  default: '#FF2E4D' },
  { key: 'reset',  label: 'Reset', type: 'button', default: 0,
    action: { type: 'increment', step: 1 }, confirm: true, tone: 'danger' },
]);
// re-render when the streamer changes something live
var lastReset = s.reset;
TE.on('settings', function (ns) {
  s = ns;
  if (ns.reset !== lastReset) { lastReset = ns.reset; resetCounter(); }
  render();
});

Control definitions are validated data. Button action accepts only increment, set or toggle. HTML, callback code and arbitrary URLs are rejected or ignored by the dashboard renderer.

07

Sandbox & limits

Widgets run in an isolated iframe with a strict content-security policy, so a broken or hostile widget can never touch your account or the rest of the page.

Allowed
  • Remote images & GIFs (TikTok gift art, avatars, any URL)
  • Google Fonts + your own @font-face
  • Audio via new Audio(url)
  • Requests to TokElements' own APIs (e.g. /api/files/…)
  • CSS animations, SVG, canvas, Web Audio
Blocked
  • External <script> tags / CDNs
  • fetch / XHR / WebSocket to other hosts
  • Cookies, localStorage, parent-page access
  • Loading npm packages
08

Examples

Complete, copy-paste widgets. Each preview is live — demo events fire so you can watch it react.

Emote reaction· pops the emote when it's sent in chat
var img = document.getElementById('emote');
// fires for every emote — use TE.onSticker('<id>', …) for one specific one
TE.on('sticker', function (ev) {
  img.src = ev.url;
  img.classList.remove('show'); void img.offsetWidth; img.classList.add('show');
});
Live preview
Follower goal bar· binds a bar to a live metric
var target = 200;                       // ← your goal
TE.on('metrics', function (m) {
  var pct = Math.min(100, m.follows / target * 100);
  document.getElementById('fill').style.width = pct + '%';
  document.getElementById('txt').textContent = m.follows + ' / ' + target;
});
Live preview