Skip to main content

Overlays

Overview

The Overlays module provides a custom streaming overlay editor where users can create, configure, and manage overlays with layered widgets. Each overlay has a unique key used for popout access in browser sources. Overlays support configurable dimensions, background settings, and multiple layer types (alerts, chat, music, custom HTML/CSS). Layers are ordered by sort_order and can be individually toggled visible/hidden.

Architecture

Backend

  • GraphQL (apps/api/src/graphql/overlays.rs) -- Full CRUD for overlays and layers. Queries for listing/fetching overlays and their layers. Mutations for create, update, delete overlays and bulk-replace layers.
  • Database (apps/api/src/db/overlays.rs) -- PostgreSQL operations for overlays and overlay_layers tables. Includes generate_overlay_key() which creates a 12-character URL-safe random key.
  • Popout Access -- Overlays are accessed via /overlay/[key]?token=xxx using popout tokens for non-expiring browser source access.

Frontend

  • Overlay editor UI in the Next.js web app.
  • Popout overlay renderer loads via the unique overlay key.
  • Layer configuration per layer type (alert config, chat style, music widget config, custom HTML/CSS/JS).

API

GraphQL Queries

QueryPermissionDescription
overlaysoverlays:readList all overlays for the account
overlay(id: UUID)overlays:readGet a single overlay by ID
overlayLayers(overlayId: UUID)overlays:readGet all layers for an overlay, ordered by sort_order

GraphQL Mutations

MutationPermissionDescription
createOverlay(input: CreateOverlayInput)overlays:createCreate a new overlay (defaults: 1920x1080, "transparent" background, "Main Overlay" name)
updateOverlay(input: UpdateOverlayInput)overlays:editUpdate overlay name, dimensions, or background
updateOverlayLayers(overlayId: UUID, layers: JSON)overlays:editBulk-replace all layers for an overlay. Each layer has: type, name, config (JSONB), visible, sort_order. Optionally include id to preserve identity.
deleteOverlay(id: UUID)overlays:deleteDelete an overlay and all its layers

REST Endpoints

All paths live under /v1/overlays. Bodies are snake_case and mirror the GraphQL inputs.

MethodPathPermissionDescription
GET/v1/overlaysoverlays:readList overlays for the account
POST/v1/overlaysoverlays:createCreate an overlay
GET/v1/overlays/{id}overlays:readGet an overlay with its layers
PATCH/v1/overlays/{id}overlays:editUpdate name, dimensions, background, or layers
DELETE/v1/overlays/{id}overlays:deleteDelete an overlay and its layers

Input Types

CreateOverlayInput:

FieldTypeDefaultDescription
nameString?"Main Overlay"Overlay name
widthi32?1920Canvas width in pixels
heighti32?1080Canvas height in pixels
backgroundString?"transparent"Background color/value

UpdateOverlayInput:

FieldTypeDescription
idUUIDOverlay ID (required)
nameString?New name
widthi32?New width
heighti32?New height
backgroundString?New background

Permissions

PermissionDescription
overlays:readView overlay configuration and layers
overlays:createCreate new overlays
overlays:editEdit overlay settings and layers
overlays:deleteDelete overlays

Database

TableDatabaseDescription
overlaysPostgreSQLid, account_id, name, key (unique 12-char URL-safe string), width, height, background, created_at, updated_at
overlay_layersPostgreSQLid, overlay_id (FK), type (layer type string), name, config (JSONB), visible (bool), sort_order (int), created_at, updated_at
overlay_access_tokensPostgreSQLid, overlay_id (FK, unique), account_id (FK), token_hash (SHA-256, unique), created_at. One token per overlay; cascade-deleted with the overlay.

Overlay Key Generation

The generate_overlay_key() function creates a 12-character random string using characters A-Z, a-z, 0-9, -, _. This key is used in the popout URL: /overlay/[key]?token=xxx.

Access Tokens

Each overlay is protected by a dedicated access token (lm_overlay_* prefix). The token is a 75-character hex-encoded string stored as a SHA-256 hash in the overlay_access_tokens table. Access requires both the overlay key (in the URL path) and a valid token — a two-secret-layer model that prevents unauthorized subscribes even if one value leaks.

Security Model

  • Anonymous WebSocket subscribes to overlay:{key} channels are blocked; a valid lm_overlay_* token is required.
  • The server re-validates the token against the database every 30 seconds (heartbeat re-validation). Revoked or rotated tokens cause an immediate disconnect with TOKEN_REVOKED.
  • The token carries no RBAC permissions and cannot access REST or GraphQL endpoints.

Token Lifecycle

EventBehavior
Overlay creation (createOverlay)A token is auto-issued and returned in the response
Copy URL (dashboard button)The existing token is revoked and a fresh token is issued (auto-rotation). The old URL stops working immediately.
Revoke (revokeOverlayToken)The token is destroyed without re-issue. Use for compromise emergencies — the overlay becomes inaccessible until a new URL is copied.
Overlay deletionThe token is cascade-deleted with the overlay row

Transport

The token is passed via ?token=lm_overlay_… query parameter (primary, for browser sources) or Authorization: Bearer lm_overlay_… header.

OBS Setup

  1. Open the overlay editor in the Lumio dashboard.
  2. Click Copy URL — this generates a fresh token and copies the full URL to the clipboard.
  3. In OBS, add a Browser Source and paste the URL.
  4. The browser source connects automatically; no further configuration is needed.

Migration for Existing Overlays

Overlays created before the token system do not have a token. Their old URLs no longer grant WebSocket access. To restore connectivity, open the dashboard and click Copy URL to generate a token-bearing URL.

Data Flow

  1. User creates an overlay via the editor. A unique key and access token are generated.
  2. User adds layers (alert, chat, music, custom) and configures each one.
  3. Layers are saved via updateOverlayLayers mutation (bulk replace).
  4. The overlay is accessed in OBS via its URL containing the overlay key and access token.
  5. The popout page loads the overlay and all its layers, connecting to WebSocket for real-time updates using the access token for authentication.

Folders

Overlays can be organized into flat folders (one level, no nesting). Each account has unique folder names.

Creating Folders

  • GraphQL: createOverlayFolder(name: String!) — requires overlays:create permission and feature:overlay_folders feature flag
  • REST: POST /v1/overlay-folders — body: { "name": "..." }

Moving Overlays

  • GraphQL: moveOverlay(overlayId: UUID!, folderId: UUID) — pass null for folderId to move back to root
  • REST: POST /v1/overlays/{id}/move — body: { "folder_id": "..." | null }

Requires overlays:edit permission, feature:overlay_folders feature flag, and per-overlay editor access.

Folder Management

OperationGraphQLRESTPermission
ListoverlayFoldersGET /v1/overlay-foldersoverlays:read
CreatecreateOverlayFolderPOST /v1/overlay-foldersoverlays:create
RenamerenameOverlayFolderPATCH /v1/overlay-folders/{id}overlays:edit
DeletedeleteOverlayFolderDELETE /v1/overlay-folders/{id}overlays:delete

All folder endpoints require feature:overlay_folders to be enabled for the account's plan.

Access Control

Per-overlay member access control refines the account-level RBAC permissions. Users with overlays:manage-access (Owner and Administrator roles by default) bypass all per-overlay restrictions.

Access Levels

LevelCan ViewCan Edit
ViewerYesNo
EditorYesYes
No AccessNoNo

Resolution Logic

  1. If user has overlays:manage-access permission → full access (bypass)
  2. If user lacks overlays:read → no access
  3. If no per-overlay entry exists → default Viewer access
  4. If entry has role none → blocked
  5. If entry has role editor → full access
  6. If entry has role viewer → view only

Endpoints

OperationGraphQLRESTPermission
List accessoverlayAccess(overlayId)GET /v1/overlays/{id}/accessoverlays:manage-access
Set accesssetOverlayAccess(overlayId, userId, role)PUT /v1/overlays/{id}/access/{userId}overlays:manage-access
Remove accessremoveOverlayAccess(overlayId, userId)DELETE /v1/overlays/{id}/access/{userId}overlays:manage-access
List candidatesoverlayAccessCandidates(overlayId)GET /v1/overlays/{id}/access/candidatesoverlays:manage-access

All access control endpoints require feature:overlay_access to be enabled.

Temporary shared overlay links allow time-limited access to a specific overlay without requiring account membership. Links use the lm_share_ token type (7th authentication type).

Token Format

  • Prefix: lm_share_ (9 characters)
  • Body: 32 random bytes, hex-encoded (64 characters)
  • Total length: 73 characters
  • Storage: SHA-256 hash in database

Allowed Durations

Links can be created with one of five fixed durations:

DurationSeconds
1 hour3600
3 hours10800
6 hours21600
12 hours43200
24 hours86400

Endpoints

OperationGraphQLRESTPermission
ListoverlaySharedLinks(overlayId)GET /v1/overlays/{id}/shared-linksoverlays:edit + editor
CreatecreateOverlaySharedLink(overlayId, durationSecs)POST /v1/overlays/{id}/shared-linksoverlays:edit + editor
ExtendextendOverlaySharedLink(linkId, durationSecs)PATCH /v1/overlay-shared-links/{id}overlays:edit + editor
RevokerevokeOverlaySharedLink(linkId)DELETE /v1/overlay-shared-links/{id}overlays:edit + editor

All shared link endpoints require feature:overlay_sharing to be enabled.

WebSocket Access

Shared links authenticate via the ?token=lm_share_... query parameter on WebSocket connections. The token grants subscription to the overlay's WebSocket channel (overlay:{key}). The server performs periodic heartbeat checks:

  • In-memory expiry: checked every heartbeat tick (5s) — disconnects with TOKEN_EXPIRED
  • DB revocation: checked every 30 seconds — disconnects with TOKEN_REVOKED

Key Files

PathDescription
apps/api/src/graphql/overlays.rsGraphQL queries and mutations
apps/api/src/db/overlays.rsDatabase operations and key generation