Skip to main content

Custom Widgets

Published 31 Jul 2026

Open Overlays in TipLink
For advanced users

This page assumes you can write HTML, CSS and JavaScript. If you only want to arrange the widgets TipLink already ships with, see Stream Overlays.

A custom widget is your own overlay element, built from plain web files, running inside a TipLink overlay canvas. It receives live event data from TipLink and can render whatever you like.


Widget Structure

A widget is a folder containing these files:

FileRequiredPurpose
widget.htmlYesYour markup.
widget.cssYesYour styles. May be empty.
widget.jsYesYour logic. May be empty.
variables.jsonNoMaps TipLink placeholders into named JavaScript variables.
settings.jsonNoDeclares a settings panel shown inside the TipLink editor.
metadata.jsonNoName and authoring info. TipLink manages this for you.
assets/NoImages, fonts, sounds and anything else you reference.

TipLink assembles these into a single index.html when the widget is saved or imported. Do not write that file yourself; it is regenerated every time and never included when you export.


  1. Open the Overlays page and scroll to the Custom Widgets section.
  2. Click Create Widget.
  3. Give it a name.
  4. Edit widget.html, widget.css and widget.js in the built-in editor.
  5. Save.

TipLink rebuilds the widget and refreshes it live, in both the editor and any open overlay, so you can iterate without restarting anything.

Widgets you build show an Authored badge in the list. Imported ones show Imported. The list also tells you whether each widget is Ready, or whether its index.html is missing because it has not been assembled yet.


Receiving Data

Static variables

variables.json maps TipLink placeholders onto names your JavaScript can read:

{
"description": "Map event data into the widget's global namespace.",
"mappings": {
"username": "{{ username }}",
"amount": "{{ value }}"
}
}

Those become properties on a global WIDGET_VARS object:

console.log(WIDGET_VARS.username);

Live event data

When the widget is triggered by a Stream Overlay action, it receives a widget-trigger message:

window.addEventListener('message', function (event) {
if (event.data && event.data.type === 'widget-trigger') {
var variables = event.data.variables || {}; // your mappings, resolved
var eventData = event.data.eventData || {}; // raw event metadata
var duration = event.data.duration || 5; // seconds this widget will show

Object.assign(WIDGET_VARS, variables);

document.getElementById('message').textContent =
variables.message || eventData.username || 'Event received!';
}
});
PropertyWhat it holds
variablesYour variables.json mappings with placeholders resolved.
eventDataThe raw event metadata: username, amount, message, currency, eventType and so on.
durationHow many seconds the widget will be displayed.

Widget settings

If you ship a settings.json, the chosen values arrive as a widget-settings message:

window.addEventListener('message', function (event) {
if (event.data && event.data.type === 'widget-settings') {
applySettings(event.data.settings);
}
});

Adding a Settings Panel

Ship a settings.json and your widget gets the same settings panel in the editor as a built-in widget. Without one, the widget still works; it just has no panel.

{
"title": "My Widget",
"settings": [
{ "type": "section", "label": "Appearance" },
{ "type": "color", "key": "bgColor", "label": "Background", "defaultValue": "#111111" },
{ "type": "range", "key": "fontSize", "label": "Font Size", "defaultValue": 24, "min": 8, "max": 96, "step": 1, "suffix": "px" },
{ "type": "toggle", "key": "showIcon", "label": "Show Icon", "defaultValue": true },
{ "type": "text", "key": "heading", "label": "Heading", "defaultValue": "Hello" }
]
}

You can also supply a bare array instead of an object, in which case the widget's own name is used as the title.

Supported setting types

TypePurpose
sectionA heading that groups the settings below it. Needs only a label.
toggleOn/off switch.
textSingle or multi-line text. Add "multiline": true for a textarea.
numberNumeric input.
rangeSlider. Takes min, max, step and an optional suffix.
selectDropdown. Takes an options array of { value, label }.
colorColour picker. Add "allowText": true to accept transparent and rgba().
fontGoogle Font picker.
image / mediaFile picker.
checkboxListMultiple choice.
repeaterA repeating group of fields.
actionA button that runs something in the widget. Takes a buttonLabel.
cssA custom CSS box.

Every entry except section needs a key, which is the name the value arrives under.

Entries with an unrecognised type, or a missing key, are dropped. If nothing valid is left, no panel is shown rather than an error being raised, so a malformed file will not break the editor.

Conditional fields

Add "showWhen": "someToggleKey" to any entry so it only appears when that toggle is on.


Importing a Widget

  1. Open the Overlays page and scroll to the Custom Widgets section.
  2. Click Import (.zip) and pick your file.
  3. Review any warnings.
  4. Confirm.

The security scan

TipLink scans imported widgets and warns about code that a display widget has no reason to contain:

FlaggedWhy
require() and Node globalsWidgets run sandboxed and have no Node access.
process.env, child_processAttempts to reach the operating system.
ipcRenderer, ipcMain, remote.requireAttempts to reach Electron internals.
nodeIntegrationAttempts to escalate privileges.
<script src="https://...">Loads and runs code from the internet at display time.
Known miner signaturesCryptocurrency mining.
Treat third-party widgets like any other download

A widget is code running on your machine while you stream. Only import widgets from people you trust, and read the warnings rather than clicking through them.

TipLink also warns when a widget looks like it was built for a different application.


Adding a Widget to a Canvas

Once created or imported, add it like any other widget:

  1. Open an overlay canvas and click Open Widget Editor.
  2. Add your custom widget from the widget list.
  3. Position and resize it.
  4. Click the gear icon for its settings panel, if it ships a settings.json.

To make it appear in response to an event, point a Stream Overlay action at it. The Alert Duration field there supports placeholders, so the display time can vary with the event.


Custom CSS

Every widget, including yours, has a Custom CSS box in its settings panel. Write it as though the widget were the whole page:

:root {
--accent: #ff4488;
}

body {
font-family: 'Outfit', sans-serif;
}

TipLink rewrites this before it is applied, so it can only affect that one widget:

  • Your selectors are scoped to that widget's wrapper element.
  • :root, html, body, :scope and & all resolve to that wrapper.
  • @media, @supports, @container and @layer blocks are scoped too.
  • @keyframes are renamed per widget, so two widgets cannot collide.
  • @font-face passes through, and @import is hoisted.
  • Malformed CSS is dropped rather than throwing, because you are editing it live.

Two copies of the same widget each carry their own CSS.

Settings win over CSS

Values chosen in the settings panel are applied as inline styles, which beat your stylesheet. Add !important when overriding a colour or size that the panel also controls.

Saving a default

You can save a CSS snippet as the default for a widget type, so new instances start pre-filled. Changing a default never touches widgets already placed on a canvas.

Defaults are stored with your settings and travel with your backups.


Testing

MethodWhat it does
▶ TestA button on every widget in the editor. Plays the widget with sample data.
Live ModeAn editor toggle that plays real alerts inside the editor as events arrive.
Test ActionsOn an alert, fires its actions immediately, including the Stream Overlay action.

Troubleshooting

The widget is blank

Open the overlay URL in a normal browser and check the developer console. Errors in widget.js stop rendering.

Settings panel does not appear

settings.json is missing, is not valid JSON, or every entry was dropped. Each entry needs a recognised type, and everything except section needs a key.

Changes are not showing

Saving rebuilds and refreshes automatically. If it seems stuck, close and reopen the editor. In OBS, use Refresh cache of current page on the browser source.

Custom CSS does nothing

Your rule is probably being beaten by an inline style from the settings panel. Add !important.

An asset will not load

Reference files relative to the widget folder, for example assets/logo.png. Remote URLs are blocked by the security scan.