Skip to content

Build a custom overlay

Bloopbot's Overlay SDK connects a page in an OBS Browser Source to the events and read-only data its owner has chosen. You render the UI. The SDK handles the V2 handshake, initial replay, reconnection, diagnostics and record refresh. This is the developer reference; streamers can follow the custom overlay setup guide.

Quick start

  1. On Overlays, create a named custom overlay. Select the event types, variables and specific records it may read, then save access.
  2. Download its transparent starter HTML. The file contains no credential. Save it on the computer running OBS.
  3. Enter that absolute file path on the overlay's card, then copy the private OBS Browser Source URL into OBS. The URL supplies ?ws= to the SDK.
  4. Keep the OBS scene visible. Refresh status should show a connected source. Use an alert layer's Test on stream… action for a live overlay check.

A named overlay starts with no grants. Your code's subscriptions narrow those grants; they never expand them. Each named overlay has its own read credential. Keep the copied OBS and socket URLs private.

Single-file browser page

The downloaded starter uses the versioned browser build supplied by Bloopbot. You can also start from this minimal file:

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>My overlay</title>
<style>html,body { margin: 0; background: transparent; color: white; font: 48px sans-serif }</style>
<div id="event" aria-live="polite"></div>
<script src="https://YOUR-BLOOPBOT-HOST/assets/overlay-sdk-0.1.0.js"></script>
<script>
  const overlay = BloopbotOverlay.createOverlay({ events: ['follow'] });
  overlay.onEvent('follow', frame => {
    document.querySelector('#event').textContent = `${frame.name ?? 'Someone'} followed`;
  });
  overlay.onDiagnostic(issue => console.warn(issue.code, issue.message));
  overlay.start().catch(error => console.error(error));
</script>
</html>

Replace the script host with the host shown in your downloaded starter. Grant follow on the named overlay before using this example. Register handlers before start() so they see replayed state. The script path is versioned; pin it rather than requesting an unversioned latest build.

npm and TypeScript

Install @bloopbot/overlay-sdk in a separate overlay project and bundle it for your HTML page:

npm install @bloopbot/overlay-sdk
import { createOverlay } from '@bloopbot/overlay-sdk';

const overlay = createOverlay({
  events: ['follow', 'stage.alert'],
  variables: ['hype'],
  records: [{ kind: 'alert_design', id: 'follow' }],
});
overlay.onEvent('follow', frame => console.log('Follow', frame));
overlay.onEvent('stage.alert', frame => console.log('Alert', frame));
overlay.onState(state => console.log(state.status, state.variables.hype));
overlay.onDiagnostic(issue => console.warn(issue.code, issue.message));
await overlay.start();

The OBS URL supplies the socket URL. For local development, pass { url: 'ws://localhost:3000/overlay/…' } as the second argument to createOverlay; use a private test credential, never commit it. start() resolves after subscription acknowledgement and the initial state replay. It rejects when the initial V2 handshake fails. Call stop() when the page is torn down.

Subscription reference

createOverlay(subscriptions, options?) returns an OverlayReader. The required events array can be empty. Optional arrays are variables, messages, boards and records. Requests are fixed for one connection; recreate the client to change them. Names are deduplicated. Up to 64 events, 32 variables, 64 messages, 32 boards and 64 records may be requested.

Request What it selects Access required
events Event families listed below Matching event grant
variables Declared variable names, such as hype Matching variable grant
messages Named custom messages, such as round.start custom event grant
boards loyalty, contrib:cheers:stream, contrib:gifts:alltime, contrib:total:stream, or var:hype leaderboard event grant; variable boards also need that variable grant
records Exact { kind, id } pairs Matching record grant

The server acknowledges accepted and rejected subscriptions. Rejections produce subscription_rejected diagnostics with kind, name and reason (unknown or not_granted); allowed subscriptions continue. The Data access panel sets event, variable and record grants. Message names and board keys are selected by the author within their family grant.

OverlayReader exposes state, start(), stop(), onEvent(family, handler), onState(handler), onDiagnostic(handler) and getRecord(kind, id). Each listener returns an unsubscribe function. getRecord requires an exact record subscription and returns the record or null when it is unavailable. It does not grant arbitrary reads.

options accepts url, handshakeTimeoutMs (1–60,000; default 5,000), reconnect (default true) and recordRefreshMs (1,000–3,600,000; default 30,000). A connection that loses access closes without reconnecting; ordinary disconnects retry with backoff up to 30 seconds.

Events and frames

Subscribe and listen using the family name. Each SDK socket message carries one event object with a type and event-specific fields. After subscription acknowledgement, Bloopbot replays retained state such as an active goal as individual events before signaling ready; register handlers before start() to receive them. Inspect a frame in your test overlay before relying on optional fields. The SDK forwards valid frames without making every event payload a fixed TypeScript shape.

Families Examples
Stream activity follow, sub, resub, gift, cheer, raid, adbreak, charity
Interactions chat, chathighlight, poll, prediction, hypetrain, giveaway, queue, wheel, minigames, custom
Widgets and display countdown, credits, contributions, emotewall, goal, jar, leaderboard, slideshow, tickerline
Stage stage.alert, stage.media, stage.minigame

Stage frames have type: 'stage'; the SDK dispatches them to the corresponding stage.* listener by kind. Grant stage.alert and the matching alert_design:name record for alert designs. stage.media covers media started by a flow. For named custom messages, grant custom and list the message names in messages.

A vars frame updates state.variables; subscribed variable values are replayed during setup. sdk.record frames and periodic reads update state.records. onState receives a new state after each update. state.lastEventAt is a millisecond timestamp or null.

State and records

state.status is idle, connecting, ready, stale, expired or stopped. stale means the connection is retrying. expired means its credential or access is no longer valid; copy the new OBS URL after rotation. state.revision is the acknowledged grant revision, or null before acknowledgement.

Records are read-only snapshots keyed as kind:id in state.records. Each has kind, id, revision and data. Available kinds are widget, giveaway, nowplaying, alert_design and theme; nowplaying:current is the current-song record. Request specific IDs and grant those same IDs in Data access. The client reads granted records after readiness and refreshes them every 30 seconds by default. It ignores older revisions and removes a missing or revoked record on refresh. Do not treat a record as a command surface.

const overlay = createOverlay({
  events: [],
  records: [{ kind: 'nowplaying', id: 'current' }],
});
overlay.onState(state => {
  const song = state.records['nowplaying:current'];
  document.querySelector('#song')!.textContent = song ? JSON.stringify(song.data) : 'No song';
});
await overlay.start();

Simulate without OBS

createSimulation(subscriptions) implements the same reader methods without a socket. Call start(), then emit(frame), setVariables(values) and setRecord(record) to exercise rendering. Simulation applies the requested subscriptions; it does not contact Twitch, OBS or Bloopbot.

import { createSimulation } from '@bloopbot/overlay-sdk';

const overlay = createSimulation({ events: ['follow'], variables: ['hype'] });
overlay.onEvent('follow', frame => console.log(frame.name));
await overlay.start();
overlay.setVariables({ hype: 42 });
overlay.emit({ type: 'follow', name: 'Ada' });
overlay.stop();

For a real end-to-end check, load the page in a visible OBS scene and trigger Test on stream… on an alert layer. That action queues a real overlay test but does not run its flow or send a Twitch event. The flow editor's Test fire checks flow decisions separately without sending to OBS.

Diagnostics and security

Listen to onDiagnostic during development. Codes include subscription_rejected, invalid_frame, connection_closed, connection_error, handler_error, unsupported_server, access_expired and record_unavailable. A rejected subscription usually means the named overlay's Data access grant is missing. unsupported_server means the endpoint did not finish the V2 handshake. A record read can be unavailable even while the socket is ready.

The OBS and socket URLs contain a private read credential. Do not include one in an HTML file, screenshot, source repository, log, AI brief or issue report. The downloaded starter and generated AI brief omit it. Rotate private URL… invalidates the old one; update every OBS source that used it. Only read-only overlay data is available through this credential; writes use separate product controls. The SDK never needs a Twitch token.

Versions and compatibility

This page describes Overlay SDK 0.1.0 and protocol V2. The npm import and /assets/overlay-sdk-0.1.0.js browser build come from the same package source. Pin the package version or script path you tested. Bloopbot supports a protocol major version for at least 12 months after a replacement is introduced; a new major is announced with its migration path before support ends. A V2 client requires a Bloopbot endpoint that supports the V2 handshake; unsupported_server is the diagnostic when setup cannot finish. The package README includes separate-repository release instructions. Check the package release notes before upgrading a deployed overlay.