Custom Widgets
Published 31 Jul 2026
Open Overlays in TipLinkThis 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:
| File | Required | Purpose |
|---|---|---|
widget.html | Yes | Your markup. |
widget.css | Yes | Your styles. May be empty. |
widget.js | Yes | Your logic. May be empty. |
variables.json | No | Maps TipLink placeholders into named JavaScript variables. |
settings.json | No | Declares a settings panel shown inside the TipLink editor. |
metadata.json | No | Name and authoring info. TipLink manages this for you. |
assets/ | No | Images, 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.
Creating a Widget in TipLink
- Open the Overlays page and scroll to the Custom Widgets section.
- Click Create Widget.
- Give it a name.
- Edit
widget.html,widget.cssandwidget.jsin the built-in editor. - 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!';
}
});
| Property | What it holds |
|---|---|
variables | Your variables.json mappings with placeholders resolved. |
eventData | The raw event metadata: username, amount, message, currency, eventType and so on. |
duration | How 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
| Type | Purpose |
|---|---|
section | A heading that groups the settings below it. Needs only a label. |
toggle | On/off switch. |
text | Single or multi-line text. Add "multiline": true for a textarea. |
number | Numeric input. |
range | Slider. Takes min, max, step and an optional suffix. |
select | Dropdown. Takes an options array of { value, label }. |
color | Colour picker. Add "allowText": true to accept transparent and rgba(). |
font | Google Font picker. |
image / media | File picker. |
checkboxList | Multiple choice. |
repeater | A repeating group of fields. |
action | A button that runs something in the widget. Takes a buttonLabel. |
css | A 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
- Open the Overlays page and scroll to the Custom Widgets section.
- Click Import (.zip) and pick your file.
- Review any warnings.
- Confirm.
The security scan
TipLink scans imported widgets and warns about code that a display widget has no reason to contain:
| Flagged | Why |
|---|---|
require() and Node globals | Widgets run sandboxed and have no Node access. |
process.env, child_process | Attempts to reach the operating system. |
ipcRenderer, ipcMain, remote.require | Attempts to reach Electron internals. |
nodeIntegration | Attempts to escalate privileges. |
<script src="https://..."> | Loads and runs code from the internet at display time. |
| Known miner signatures | Cryptocurrency mining. |
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:
- Open an overlay canvas and click Open Widget Editor.
- Add your custom widget from the widget list.
- Position and resize it.
- 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,:scopeand&all resolve to that wrapper.@media,@supports,@containerand@layerblocks are scoped too.@keyframesare renamed per widget, so two widgets cannot collide.@font-facepasses through, and@importis 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.
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
| Method | What it does |
|---|---|
| ▶ Test | A button on every widget in the editor. Plays the widget with sample data. |
| Live Mode | An editor toggle that plays real alerts inside the editor as events arrive. |
| Test Actions | On 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.
Related
- Stream Overlays - canvases and the built-in widgets.
- Stream Overlay action - triggering a widget from an alert.
- Using Event Data in Actions - the placeholders you can map in
variables.json.