Build custom Twitch overlays and alerts in HTML and CSS, powered by live data tags, real-time controls, and external integrations.
There is no drag-and-drop editor. There is no proprietary file format. There is no runtime you do not control. Your overlay is a webpage, and Overlabels is the engine that keeps it alive.
Overlabels uses a deliberate, collision-resistant template syntax: triple square brackets. It is distinctive enough to never clash with HTML, CSS, JavaScript, or any template engine you might encounter in the wild, and simple enough to understand without documentation.
<span class="followers">[[[followers_total]]]</span>That is it. Drop a tag anywhere in your HTML or CSS, and Overlabels replaces it with live data when the overlay renders. The entire system - static data, live controls, conditional blocks - runs through this one syntax.
Template tags are resolved from live Twitch API data fetched on overlay load and updated in real time when events fire. Every tag maps deterministically to a value in the Twitch Helix API response.
| Tag | Description |
|---|---|
[[[user_name]]] |
Display name of the broadcaster |
[[[user_broadcaster_type]]] |
affiliate, partner, or empty |
[[[user_description]]] |
Channel bio |
[[[user_view_count]]] |
All-time view count |
[[[user_avatar]]] |
Profile image URL |
[[[channel_title]]] |
Current stream title |
[[[channel_game]]] |
Current game/category |
[[[channel_language]]] |
Broadcast language code |
[[[channel_tags_0]]] - [[[channel_tags_10]]] |
Individual stream tags by index |
| Tag | Description |
|---|---|
[[[followers_total]]] |
Total follower count |
[[[followers_latest_user_name]]] |
Most recent follower's display name |
[[[followers_latest_date]]] |
Timestamp of most recent follow |
[[[subscribers_total]]] |
Total subscriber count |
[[[subscribers_points]]] |
Subscriber points total |
[[[subscribers_latest_user_name]]] |
Most recent subscriber |
[[[subscribers_latest_tier]]] |
Subscription tier (1000, 2000, 3000) |
[[[subscribers_latest_is_gift]]] |
Whether the latest sub was gifted |
| Tag | Description |
|---|---|
[[[goals_latest_type]]] |
Goal type (follower, subscription, etc.) |
[[[goals_latest_current]]] |
Current progress value |
[[[goals_latest_target]]] |
Target value |
[[[goals_latest_description]]] |
Goal description |
Tags can be used anywhere in your HTML or CSS. Font sizes, colour values, content attributes, aria-label -
wherever a value belongs, a tag can go.
.follower-bar {
width: calc([[[followers_total]]] / [[[goals_latest_target]]] * 100%);
}Controls are typed, persistent, mutable values attached to a template. They are the state layer of Overlabels. Define a control once, reference it anywhere in your overlay, and update it from your dashboard while the overlay is live. The update propagates instantly over WebSocket, no reload required.
Reference a control in your template with the c: prefix:
<span class="username">[[[c:myname]]]</span>| Type | Stores | Use Case |
|---|---|---|
text |
String (up to 1000 chars, HTML stripped) | Names, messages, labels |
number |
Numeric string | Scores, goals, counters with arbitrary values |
counter |
Integer string | Kill counters, death counters, incrementable values |
timer |
Derived from config state | Countup, countdown, and count-to-datetime timers |
datetime |
ISO 8601 string | Scheduled events, stream start times |
boolean |
"1" or "0" |
Feature flags, show/hide blocks, on/off state |
expression |
Derived from formula | Computed values that reference other controls |
Each control has a key (alphanumeric and underscore, used in your template), a label (shown in your dashboard),
and a type that determines how the value is sanitised, resolved, and displayed.
The timer control type is a first-class citizen with three modes:
- Count up: Starts at zero and counts upward
- Countdown: Starts at a configured duration and counts down to zero
- Count to: Counts down the remaining time until a target date and time
<div class="timer">[[[c:round_timer]]]</div>Timer config drives everything: mode, base_seconds, offset_seconds, running, started_at,
and target_datetime.
The display value is derived deterministically from these fields.
There is no mutable tick state to drift or desync. Count-to timers always tick and need no start/stop interaction.
You can read timer values in your Controls, so you can use them to perform complex calculations through Expressions Controls.
Expression controls are formulas that reference other controls and evaluate entirely client-side with zero latency. They enable derived values without any server round-trips.
c.streamlabs.latest_donor_at > c.kofi.latest_donor_at
? c.streamlabs.latest_donor_name
: c.kofi.latest_donor_name
Use them in your template like any other control:
<span class="latest-donor">[[[c:latest_donor]]]</span>Expressions re-evaluate reactively whenever any referenced control value changes. They support arithmetic, comparisons, ternary operators, and can reference controls from any source, including external integrations.
Every control has a companion _at value containing the Unix timestamp of its last update. These are available as
template tags and in expressions:
<span class="updated">[[[c:death_counter_at]]]</span>This enables cross-service timing comparisons - for example, showing whichever donation arrived most recently across Ko-fi and StreamLabs.
Combine boolean controls with the conditional syntax to show or hide entire overlay sections from your dashboard without touching the template:
[[[if:c:show_donations]]]
<div class="donation-bar">...</div>
[[[endif]]]Flip the control in your dashboard. The overlay responds instantly.
Each template supports up to 20 50 controls. Every control value update is broadcast as a control.updated
event on the alerts.{twitch_id} WebSocket channel. The overlay receives the update, replaces the tag value in
reactive state, and re-renders the affected nodes. The rest of the overlay is untouched.
Overlabels includes a full conditional rendering engine evaluated client-side in the overlay. Any template tag - whether it references Twitch API data, a control value, or an event payload - can drive a conditional block.
[[[if:variable operator value]]]
...
[[[elseif:variable operator value]]]
...
[[[else]]]
...
[[[endif]]]| Operator | Meaning |
|---|---|
= |
Equal |
!= |
Not equal |
> |
Greater than |
< |
Less than |
>= |
Greater than or equal |
<= |
Less than or equal |
Numeric comparisons are numeric. String comparisons are lexicographic. Truthiness checks ([[[if:c:show_alerts]]])
treat "0", "false", empty string, null, and undefined as falsy - everything else is truthy.
[[[if:channel_language = en]]]
<p>English stream</p>
[[[elseif:channel_language = es]]]
<p>Stream en Espanol</p>
[[[else]]]
<p>Welcome</p>
[[[endif]]]
[[[if:followers_total >= 1000]]]
<div class="milestone">1K Followers reached</div>
[[[endif]]]
[[[if:subscribers_latest_is_gift]]]
<span>Gift sub incoming</span>
[[[endif]]]Conditionals evaluate after tag replacement, so the full resolved value of any tag - including live control values - is available to every comparison. Nesting is supported up to 10 levels deep.
Event alert templates are separate overlays triggered by Twitch EventSub events. They share the full template tag syntax and support all control references and conditional blocks. When a Twitch event fires, Overlabels renders the assigned alert template with the event payload merged into the tag context, broadcasts it to the overlay via WebSocket, and displays it with a configurable transition and duration.
| Event | Twitch EventSub Type |
|---|---|
| New Follower | channel.follow |
| New Subscription | channel.subscribe |
| Gift Subscriptions | channel.subscription.gift |
| Resubscription | channel.subscription.message |
| Bits Cheer | channel.cheer |
| Raid | channel.raid |
| Channel Points Redemption | channel.channel_points_custom_reward_redemption.add |
| Stream Online | stream.online |
| Stream Offline | stream.offline |
Each event type exposes its payload as template tags. A few examples:
Follows
<span>[[[event.user_name]]] just followed!</span>Subscriptions
<span>[[[event.user_name]]] subscribed at Tier [[[event.tier]]]</span>Gift Subscriptions
<span>[[[event.user_name]]] gifted [[[event.total]]] subs!</span>Raids
<span>[[[event.from_broadcaster_user_name]]] is raiding with [[[event.viewers]]] viewers</span>Bits
<span>[[[event.user_name]]] cheered [[[event.bits]]] bits</span>By default, alerts fire on every connected static overlay. Alert targeting lets you restrict specific alerts to specific static overlays. This is useful when you have multiple scenes in OBS, for example, showing donation alerts only on your main gameplay overlay, not on your BRB screen.
If no targeting is configured, the alert fires everywhere (backward-compatible default).
The Alerts Builder maps event types to alert templates. Each mapping stores a display duration (in milliseconds), a transition effect, and an enabled flag. You can disable individual alert types without removing the mapping.
Alerts require a static overlay to act as a container. Without a running static overlay on the same browser source, there is no DOM to render into.
Overlabels connects to external services that drive controls and trigger alerts alongside Twitch events. Each integration normalises incoming data into the same control and alert pipeline, your template syntax stays the same regardless of the source.
Ko-fi webhooks deliver donation, subscription, shop order, and commission events. Each event can trigger alerts and update controls.
Controls use the kofi: namespace:
<span>[[[c:kofi:donations_received]]]</span>
<span>[[[c:kofi:latest_donor_name]]]</span>Ko-fi controls are added manually from presets in the control editor when the integration is connected.
StreamLabs connects via OAuth and delivers donation events through a Socket.IO bridge. On connection, six controls are automatically provisioned:
| Control | Description |
|---|---|
streamlabs:donations_received |
Total donation count |
streamlabs:latest_donor_name |
Most recent donor |
streamlabs:latest_donation_amount |
Most recent donation amount |
streamlabs:latest_donation_message |
Most recent donation message |
streamlabs:latest_donation_currency |
Most recent donation currency |
streamlabs:total_received |
Total amount received |
Reference them like any other control:
<div class="donation">
[[[c:streamlabs:latest_donor_name]]] donated
[[[c:streamlabs:latest_donation_amount]]] [[[c:streamlabs:latest_donation_currency]]]
</div>All external services follow the same pipeline:
- Incoming webhook or socket event is verified and parsed
- The event is deduplicated and stored
- Matching controls are updated and broadcast in real time
- If an alert template is mapped to the event type, it fires through the standard alert pipeline
Service-managed controls (auto-provisioned by integrations) cannot be manually edited, they update only when the service sends new data.
Understanding the pipeline removes all mystery from how Overlabels works.
- A browser source in OBS loads
https://overlabels.com/overlay/{slug}#token - The token in the URL fragment is extracted client-side - it is never sent to the server
- The frontend initialises a WebSocket connection via Laravel Reverb and mounts the
OverlayRendererVue component - The renderer fetches template data and live Twitch values using the token
- Tag replacement runs:
[[[tag]]]patterns are replaced with resolved values - Expression controls are registered and evaluated client-side
- Conditional blocks are evaluated and non-matching branches are removed from output
- CSS is injected into the document head via a
<style id="overlay-style">tag - The rendered HTML is mounted into the overlay DOM
- The WebSocket channel
alerts.{twitch_id}is subscribed for live updates
After initial render, the overlay stays live through three update mechanisms:
- Control updates:
control.updatedWebSocket events trigger reactive re-renders of affected tags and - re-evaluation of expressions that reference the changed control
- EventSub events:
channel.follow,channel.subscribe, etc. cause relevant aggregate tags - (e.g.,
followers_total) to update - Alert triggers: Alert templates are broadcast as complete render payloads, displayed over the static overlay,
- and auto-dismissed after their configured duration
The overlay includes built-in resilience for the unpredictable environment of OBS browser sources:
- Automatic reconnection with exponential backoff when the WebSocket drops
- Periodic health checks to detect stale connections
- Visual error banners (since OBS browser sources cannot show console output)
- Auto-reload as a last resort when recovery fails
- A Twitch EventSub webhook or external service event arrives
- The event is validated (HMAC-SHA256 for Twitch, per-service verification for externals)
- The event is stored and broadcast over the internal queue
- A mapping lookup finds the assigned template for this event type and user
- Current overlay data is merged with the event payload
- The compiled alert is broadcast to
alerts.{twitch_id}via WebSocket - The overlay frontend receives the payload, checks alert targeting rules, and renders into the alert DOM node
- The configured transition plays, the alert displays for
duration_msmilliseconds, then auto-dismisses
<script>, <embed>, <iframe> and any other scary tag are stripped from all template content - head, html,
css, and meta fields - before storage. This is visualised client-side but also enforced server-side
before form submission. External stylesheets, font libraries, icon libraries,
and CDN-hosted CSS are all permitted. Inline scripts are not. Neither are embeds. This is to prevent
XSS attacks and to prevent accidental embedding of malicious content. If you need to embed a third-party
script, you're out of luck. Simple as that.
An Overlay Kit is a named collection of templates designed to work together as a cohesive system. A kit might include a static overlay, a follower alert, a subscription alert, a raid alert, and a channel points redemption alert. All sharing a visual language, matching CSS variables, and a consistent tag vocabulary.
Kits can be copied in a single action. Copying a kit creates duplicates of every template it contains, owned by you, ready to customise.
Note: Kits are user-generated content. They can contain anything you like, or nothing you want. Have a good look at the Kit's contents before you copy it to your own account.
Any public template or kit can be copied. Copying creates a full independent duplicate owned by you. The original is untouched. Your copy is yours to modify, extend, or break however you like.
When copying a template that has Controls, the Import Wizard walks you through which controls to carry over. Control references in the template are preserved as-is - you pick what state comes with the copy.
Public templates are public by default. Privacy is opt-in. There are no paywalls, no tiers, no licensing restrictions.
When a new account is created, Overlabels runs an automated onboarding pipeline in the background:
- Webhook secret - A per-user 32-byte hex secret is generated for HMAC validation
- Starter kit: The configured starter kit is automatically copied into your account,
- giving you a working set of templates on day one
- Alert assignment: Copied templates are matched to event types by keyword detection and
- auto-assigned in the Alerts Builder
- Tag generation: A background job fetches your live Twitch data and generates your full personalised tag set
The onboarding status endpoint tracks each step: kit_forked, tags_status, alerts_mapped, token_created,
and has_webhook_secret. The dashboard reflects this state in real time as each step completes.
The /testing page provides a personalised testing environment for every supported Twitch event.
Each command is pre-filled with your Twitch ID, your webhook URL, and your per-user webhook secret,
ready to copy and run directly in your terminal via the Twitch CLI.
twitch event trigger channel.follow \
--transport=webhook \
-F https://overlabels.com/api/twitch/webhook \
-s your_webhook_secret \
--to-user your_twitch_id \
--from-user 1234567Commands are blurred by default and revealed on hover. Don't leak these commands on stream!
Overlay access is authenticated via 64-character hexadecimal tokens (256 bits of randomness). Tokens are:
- Hashed on storage: only the SHA-256 hash is stored server-side; the plain token is shown once
- Never transmitted in the URL path: passed as a URL fragment (
#token), which browsers do not include - in HTTP requests
- Revocable: a compromised token can be disabled or deleted without affecting other tokens
- Optionally expiring: set an
expires_atto time-limit overlay access - Optionally IP-restricted: bind a token to a specific IP or CIDR range
Public overlays additionally support hash-based shareable links that require no token at all, intended for public display or embedding without authentication.
- Generate a static overlay in Overlabels. Every alert template requires a static overlay to render into.
- A big "Add to OBS" button appears in the template viewer. Click it and a secure overlay URL is copied to your clipboard. You can also drag the button directly into OBS and it will generate a Browser Source with perfect defaults for you. This is the recommended way to add an overlay to OBS
- If you want to manually add a browser source, copy the URL and paste it into a new Browser Source in OBS.
- Set the source dimensions to match your canvas (typically 1920x1080).
- Your overlay is live!
When a Twitch event fires - a follow, a sub, a raid - or an external event arrives - a Ko-fi donation, a StreamLabs tip… The alert template renders over the static overlay, transitions in, displays for the configured duration, and transitions out. No interaction required.
| Layer | Technology |
|---|---|
| Backend | Laravel 12, PHP 8.4 |
| Frontend | Vue 3 (Composition API), TypeScript |
| Styling | TailwindCSS v4 |
| UI Components | RekaUI/Shadcn/Vue |
| Full-stack bridge | Inertia.js |
| Real-time | Laravel Reverb (self-hosted WebSocket) |
| Code editor | CodeMirror |
| Database | PostgreSQL |
| Queue | Redis |
| Build | Vite |
No support is provided when you self-host Overlabels. The following instructions are for development and testing.
git clone https://github.com/jasperfrontend/overlabels
cd overlabels
composer install
npm install
cp .env.example .env
php artisan key:generate
php artisan migrate
npm run build
composer run devRequired environment variables:
TWITCH_CLIENT_ID=
TWITCH_CLIENT_SECRET=
APP_URL=https://your-public-url.com
APP_STARTER_KIT_ID=
QUEUE_CONNECTION=redis
CACHE_DRIVER=redis
DB_CONNECTION=pgsql
BROADCAST_CONNECTION=reverbOptional (for external integrations):
STREAMLABS_CLIENT_ID=
STREAMLABS_CLIENT_SECRET=
STREAMLABS_LISTENER_SECRET=Your APP_URL must be publicly reachable for Twitch EventSub webhooks to deliver.
For local development, use ngrok or a similar tunnel.
Overlabels is built on one assumption: streamers who can write a <div> should not be locked out of good
overlays by proprietary tools.
- The triple-bracket syntax exists because simplicity is different from limitation.
- The Controls system exists because overlays should respond to a streamer's intent, not just Twitch's event stream.
- External integrations exist because a streamer's ecosystem extends beyond Twitch.
- The copying system exists because good design compounds. Every overlay that exists is a starting point for the next one.
- Overlabels overlays are free forever, for anyone, anytime. No paywalls. No tiers. Loose limits.
- You can create up to 1000 overlays per account.
- Each overlay can have up to 50 Controls.
- We do not offer asset hosting.
- Any script, iframe, embed is stripped from your overlay templates before saving.
Note
The Android GPS Location app is still in active development and not ready for release just yet.
- You can only use the app to display a single location.
- You can only send GPS location data to Overlabels every 5–60 seconds (user setting).
Good question. First and foremost: Overlabels' footprint is actually rather small. The whole backend is hosted on a few instances and the total hosting costs are negligible. In fact, the monthly fee for my PhpStorm licence is more expensive than the cost of hosting Overlabels itself.
Don't worry. I have plans ❤. For now, Overlabels is small and easy to maintain.
Of course you're free to send me a Ko-fi or StreamElements tip if you like what I do! Be sure to mention this README somewhere in your tip so I can link your support back to Overlabels.
If you have questions, ideas, or improvements, open an issue or submit a pull request. Overlabels grows best when people build on top of it.
~ JasperDiscovers