Custom Overlays
π§© Custom Overlay Layer
This layer is a real-time, configurable overlay component with support for dynamic events. It includes five main tabs:
- HTML β Defines the static layout of the UI
- CSS β Custom styling rules
- JS β Handles real-time logic and event listeners
- Configs β Input schema used to customize the component
- Data β Holds the actual values for the defined fields
Chat GPT Lumia Overlay Assistantβ
We offer a Chat GPT Lumia overlay assistant that can help you with your custom overlay creation. Ask it a question like Create an overlay that changes color every time a new chat command comes in. It knows our documentation and can help out a ton
Access it here: Chat GPT Lumia Overlay Assistant
Overlay Scopeβ
Custom Overlays should handle visual rendering, chat and alert listeners, HFX and virtual light reactions, browser API calls, polling, commands, chatbot messages, storage, variables, games, and animations directly in the five overlay tabs.
Use another Lumia workflow only when the user explicitly needs code that runs outside an overlay session, packaging/distribution, Node-only dependencies, or reusable integration logic across multiple Lumia features.
πΌοΈ HTML Tabβ
Defines the layout of the overlay visible to users:
<div style="padding:1em; font-family:sans-serif;">
<h3 id="msg">Waiting for dataβ¦</h3>
<h4>Data:</h4>
<div id="data"></div>
<!-- You can use variables directly within HTML -->
<div>{{myvar}}</div>
</div>
π¨ CSS Tabβ
Customize the visual style of your overlay:
#msg {
font-weight: bold;
/* You can use variables directly within CSS */
color: {{mycolor}};
}
π§ JS Tabβ
Javascript handles initialization and real-time event updates for your overlay:
// window.Overlay is the primary API. You can use it as window.Overlay or Overlay.
// Overlay.data contains the current Data tab values.
console.log(Overlay.data);
// Call a command in Lumia Stream and pass local variables for that command run.
// In the command, you can reference {{secret}} because it is passed here.
await Overlay.callCommand("mycommand", { secret: "password" });
// Send a chatbot message to one platform, or omit platform for all connected platforms.
await Overlay.chatbot({ message: "This works", platform: "twitch", chatAsSelf: false });
// Add or subtract loyalty points for a user.
const points = await Overlay.addLoyaltyPoints({ value: 100, username: "lumiastream", platform: "twitch" });
await Overlay.addLoyaltyPoints({ value: -100, username: "lumiastream", platform: "twitch" });
// Get the amount of loyalty points that a user has.
const usersPoints = await Overlay.getLoyaltyPoints({ username: "lumiastream", platform: "twitch" });
// Create or update a global Lumia variable.
await Overlay.setVariable("myvar", "this works");
// Retrieve a variable. Always use a string literal key.
const variable = await Overlay.getVariable("myvar");
// If the variable was just created, variable replacement may require saving and refreshing once.
const usedMyVar = "{{myvar}}";
// You can use await at the top level because Lumia wraps the JS tab in an async function.
// Save an item to storage
await Overlay.saveStorage("mydata", 151);
// Get an item from storage
const pokemonCaught = await Overlay.getStorage("mydata");
// Delete an item from storage
await Overlay.deleteStorage("mydata");
Overlay.on("chat", (data) => {
const username = data.username;
const message = data.message;
toast(`New chat message received from ${username}. They said ${message}`);
});
// Full chat payload shape.
Overlay.on("chat", (data) => {
const {
origin, // Platform origin, such as "twitch", "youtube", "kick", "tiktok", or "facebook"
id, // Platform message id when available
username, // Account username/login
displayname, // Display name when available
channel, // Channel or room where the message was sent
avatar, // User avatar URL when available
message, // Chat message text
color, // User chat color, usually a hex value
badges, // Badge ids or badge image URLs
badgesLookup, // Badge id to image URL lookup map for some platforms
badgesRaw, // Raw platform badge string
emotesRaw, // Raw platform emote string
emotesPack, // Platform-specific emote metadata
isCheer, // Whether the message includes a cheer/bits event
reply, // Reply metadata: { username, body }
isPowerup, // Twitch Power-up flag
powerupType, // Twitch Power-up type
powerupName, // Twitch Power-up animation name
sharedMessage, // Twitch shared chat source metadata
lumiauserlevels, // Numeric Lumia user level ids
userLevels, // Boolean role map: isSelf, broadcaster, mod, vip, tier3, tier2, subscriber, regular, follower, anyone
time, // Local HH:mm:ss time generated by Lumia
timestamp, // Millisecond timestamp generated by Lumia
} = data;
console.log(origin, displayname || username, message, {
id,
channel,
avatar,
color,
badges,
badgesLookup,
badgesRaw,
emotesRaw,
emotesPack,
isCheer,
reply,
isPowerup,
powerupType,
powerupName,
sharedMessage,
lumiauserlevels,
userLevels,
time,
timestamp,
});
});
Overlay.on("alert", (data) => {
console.log("alert", data);
const settings = data.extraSettings || {};
const username = settings.username || "unknown";
if (data.alert === "twitch-subscriber") {
if (data.dynamic.isGift) {
console.log(`${username} sent ${data.dynamic.giftAmount} with a tier ${settings.subPlan} sub to ${settings.recipients ?? settings.recipient}`);
} else if (data.dynamic.isResub) {
console.log(`${username} shared their ${settings.subPlan} sub. They've been subscribed for ${data.dynamic.subMonths} months`);
} else {
console.log(`${username} subscribed with a tier ${settings.subPlan} sub`);
}
}
if (data.alert === "twitch-raid") {
console.log(`${username} just raided with ${data.dynamic.value} viewers`);
}
if (data.alert === "kick-follower") {
console.log(`${username} just followed on Kick`);
}
});
Overlay.on("hfx", (data) => {
const username = data.username;
const command = data.command;
const message = data.message; // If the HFX was triggered with a message
console.log(`${username} just triggered HFX ${command}`);
});
Overlay.on("virtuallight", (data) => {
console.log("virtuallight", data);
const virtualLightId = data.uuid;
const brightness = data.brightness;
if (data.color) {
const { r, g, b } = data.color;
console.log(`Light ${virtualLightId} changed to rgb(${r}, ${g}, ${b}) at ${brightness}%`);
} else if (data.power === false) {
console.log(`Light ${virtualLightId} is turning off`);
}
});
// Only matching codeId values trigger this listener.
// codeId can only contain letters, numbers, hyphens, and underscores. Max 25 characters.
Overlay.on("overlaycontent", (data) => {
const content = data.content;
console.log(`Content has been sent from Lumia Stream ${content}`);
});
We wrap all the code in an async function, so you can use await in your root level of the code without wrapping it in an async funtion. This example will have no problem
const variable = await Overlay.getStorage('mykey');
console.log(variable);
What This Doesβ
- Displays the initial data provided via
Overlay.data - Accepts incoming events in real time
π‘ Event Handlingβ
Use the Overlay.on to listen to events
Example Eventβ
If an event with type chat is dispatched and you have Overlay.on('chat') within your code it will send it to that listener
Overlay.on('chat', (data) => {
console.log('chat', data);
});
Overlay.on('alert', (data) => {
console.log('alert', data);
});
Overlay.on('hfx', (data) => {
console.log('hfx', data);
});
Overlay.on('virtuallight', (data) => {
console.log('virtuallight', data);
});
Overlay.on('overlaycontent', (data) => {
console.log('overlaycontent', data);
});
Lumia auto-detects event subscriptions from literal Overlay.on("event", ...) calls in the JS tab. Use direct string literals so the overlay subscribes to the needed events. The Data tab only needs an events array for legacy/manual cases.
For full event payload shapes, see the Overlay Type Definitions. The raw declarations live in custom-overlays.d.ts as ChatEvent, AlertEvent, HfxEvent, VirtualLightEvent, and CustomOverlayContentEvent.
OverlayListener typesβ
| Value | Label |
|---|---|
| chat | Chat messages |
| alert | Alerts |
| hfx | HFX |
| virtuallight | Virtual lights |
| overlaycontent | Custom Overlay Content |
π‘ Performance Tip: Only selected events are delivered to the overlay. π‘ The TypeScript types for chat, alerts, HFX, virtual lights, and overlay content are within the Types tab. π‘
overlaycontentsends custom content only to custom overlay layers with the matchingcodeId.
Showing Toastsβ
You can display toast notifications and log messages using the following:
toast('Message');
// Or you can pass in the type of toast to show
toast('Message', 'info');
toast('Message', 'success');
toast('Message', 'warning');
toast('Message', 'error');
Showing Logs in consoleβ
You can display log information in your console
console.log('Message');
Calling Commands in Lumia Stream using the Overlayβ
We expose an api that allows you to call a chat command and update variables
This can be used under Overlay.callCommand
Overlay.callCommand('caught');
// Or you can pass local variables to your command as well that will only be used for this instance. This will not be a global variable
Overlay.callCommand('caught', { username: 'lumia', pokemon: 'lugia', shiny: true });
Setting/Updating Global Variables in Lumia Streamβ
We expose an api that allows you to set a variable within Lumia Stream that can be used to save data to Lumia Stream and show it within commands, chatbots, or even other Overlay Layers.
We pull in varaibles dynamically so that overlays that are not using variables will not need to get the data for variables that it does not care about. This means that if you create a brand new variable within an overlay you may need to save and refresh so that it updates to pull in the new variable. But after the variable is created it will dynamically change.
This can be used under Overlay.setVariable
Overlay.setVariable('pokemon_caught', 151);
Using Persistent Storage in Lumia Streamβ
We expose an api that allows you to get, update, and delete storage tied to your codeId. This persists across Lumia Stream and can be used to communicate with your overlays in any place: OBS, Browser, Meld, etc. If you want to persist data this is the recommended way, along with variables. Note that Lumia Stream commands and chatbots cannot read this storage currently β only your Overlays can.
First-load note:
Overlay.getStorage(key)returnsnullwhen the key has never been saved, and Lumia will show a red error toast in that case. Always default the value and seed the key on first load:let counter = await Overlay.getStorage('counter');
if (counter == null) {
counter = 0;
await Overlay.saveStorage('counter', counter);
}
This can be used under Overlay.saveStorage, Overlay.getStorage, Overlay.deleteStorage
// Save an item to storage
await Overlay.saveStorage('pokemon_caught', 151);
// Get an item from stoage
const pokemonCaught = await Overlay.getStorage('pokemon_caught');
// Delete an item from stoage
await Overlay.deleteStorage('pokemon_caught');
Data Storage Options in Custom Overlaysβ
Use one of these three. Do not use browser localStorage or sessionStorage in overlays β they are not shared across OBS/browser sources and are explicitly disallowed.
1. Lumia Stream Variablesβ
- Scope: Global within Lumia Stream. Accessible by overlays, chatbots, commands, and other Lumia features.
- Persistence: Saved on the server and available across all overlays and sessions.
- Use Case: Shared state that chatbots/commands/other overlays need to read or write.
- Limitations: Variables are global β pick unique names.
- API:
Overlay.setVariable(name, value)/Overlay.getVariable(name).
2. Overlay.saveStorage / getStorage / deleteStorageβ
-
Scope: Persistent storage tied to your overlay's
codeId. Shared across every overlay instance running on the same Lumia Stream server (OBS, browser, Meld, etc.). -
Persistence: Persists across overlay reloads and app restarts.
-
Use Case: Overlay-specific state that other overlays with the same
codeIdneed to see. -
Limitations: Overlays only β Lumia commands and chatbots cannot read it. Not synced across servers.
-
First-load note:
getStoragereturnsnullwhen the key has never been saved and the host will display a red error toast. Always default the value and seed it on first load:let counter = await Overlay.getStorage("counter");
if (counter == null) {
counter = 0;
await Overlay.saveStorage("counter", counter);
}
3. Overlay.callCommandβ
- Scope: Triggers a Lumia command that can then update variables or run any Lumia action.
- Use Case: Let a command own the logic (e.g., loyalty-point math, chatbot replies) while the overlay just invokes it.
Summary Tableβ
| Method | Scope | Accessible By | Best For |
|---|---|---|---|
| Lumia Stream Variables | Global (Lumia Stream) | Overlays, commands | Shared/global state, cross-feature access |
Overlay.saveStorage etc. | Per overlay codeId/server | Overlays only | Overlay-specific persistent data |
Overlay.callCommand | Custom (via command logic) | Overlay & commands | Advanced workflows, custom logic |
Tip: Use Variables when other Lumia features need the value; use
Overlay.saveStoragewhen only the overlay needs it.
π Configs Tabβ
The Configs fields define what users can customize in the layer.
They appear on the right-hand side of the UI under the Configuration section unless a field is marked hidden: true.
A field object can now contain these properties:
| Property | Required | Purpose | Example |
|---|---|---|---|
type | β | UI control to render. Must be one of the ConfigsFieldType enum values (input, textarea, number, checkbox, dropdown, multiselect, colorpicker, fontpicker, slider, imageupload, soundupload, videoupload, actionbutton). | "type": "dropdown" |
label | β | Human-readable name shown in the sidebar. | "label": "Favorite Color:" |
value | β | Default value that appears the first time the user opens the overlay (also pre populates Overlay.data). Omit it to leave the field blank/unchecked on first load. | "value": 18 |
options | βοΈ* | Key value map of selectable choices. Required only for dropdown, multiselect and slider; ignored for other types. For slider, options supports step, min, max, prefix, suffix. | "options": { "step": 5, "min": 0, "max": 100 } |
order | β | Display order priority. Fields with lower numbers appear first. Fields without order appear after ordered fields, sorted alphabetically by key. | "order": 1 |
visibleIf | β | Conditional render rule. Field is shown only if Overlay.data[visibleIf.key] strictly equals one of the values in visibleIf.equals. Pass an array of rules to require that every rule matches (AND across keys). | "visibleIf": [{ "key": "targetKey", "equals": ["yes", "maybe"] }, { "key": "mode", "equals": "advanced" }] |
hidden | β | Hard-hide rule. When set to true, the field is never displayed in the Configs sidebar, preventing end users from altering it. The value still flows into Overlay.data, so the overlay can rely on it internally.Useful for locking event subscriptions or other advanced settings. | "hidden": true |
Additional properties for text fields (type: "input" and type: "textarea"):
| Property | Required | Purpose | Example |
|---|---|---|---|
placeholder | β | Placeholder text inside the field. | "placeholder": "Enter title..." |
enableVariables | β | When true, renders a variable-enabled field that lets users insert variables (e.g., {{username}}) from a picker. | "enableVariables": true |
allowedVariables | β | When present with enableVariables: true, limits the top of the picker to this list. System/function variables are still available below. | "allowedVariables": ["username", "message"] |
rows | β | textarea only. Visible row count. Defaults to 4. | "rows": 6 |
Additional properties for media upload fields (type: "imageupload", "soundupload", "videoupload"):
| Property | Required | Purpose | Example |
|---|---|---|---|
value | β | Default asset URL. The field's Overlay.data value is always the uploaded asset's URL string. | "value": "https://.../logo.png" |
accept | β | Optional comma-separated MIME accept hint for the upload picker. | "accept": "image/png,image/jpeg" |
Additional properties for the action button (type: "actionbutton"):
| Property | Required | Purpose | Example |
|---|---|---|---|
buttonLabel | β | Button-text override. Defaults to the field label. The action button has no persisted value β clicking it fires the configAction event. | "buttonLabel": "Reset game" |
Variable-enabled Input Fieldsβ
You can enable Lumia variables directly in text inputs by setting enableVariables: true. Optionally provide allowedVariables to surface a curated list first, and placeholder for UX.
Example:
{
"title": {
"type": "input",
"label": "Title",
"enableVariables": true,
"allowedVariables": ["username", "message"],
"placeholder": "Enter title or insert variables"
}
}
Clicking the {} adornment opens the variables panel. Selecting an item inserts it at the cursor as {{variable_name}}.
π Keys vs. Field Objectsβ
Before looking at the individual properties (type, label, value, options), remember that the JSON key itself is critical:
{
"age": {
/* field object */
},
"platform": {
/* field object */
}
}
| Concept | What it is | Where it shows up |
|---|---|---|
Key ("age", "platform", β¦) | The property name that wraps a field object | Becomes Overlay.data.age, Overlay.data.platform, etc. β this is what your JS code reads & writes |
Label ("Age:", "Platform:") | UI text shown in the sidebar next to the control | Only visible to the user; never appears in Overlay.data |
Rule of thumb: Choose short, machine-friendly keys (no spaces, camelCase is fine). Use labels for anything human-readable.
Supported Field Typesβ
| type | UI Control | Overlay.data value |
|---|---|---|
"input" | Single-line text box | string |
"textarea" | Multi-line text area | string |
"number" | Numeric input spinner | number |
"checkbox" | Checkbox selection | boolean |
"dropdown" | Select menu | string (selected option key) |
"multiselect" | Multi-select box | string[] (selected option keys) |
"colorpicker" | Color picker widget | string (hex/rgba) |
"fontpicker" | Font picker (Google) | string (font family name) |
"slider" | Number slider | number |
"imageupload" | Image upload picker | string (uploaded asset URL) |
"soundupload" | Audio upload picker | string (uploaded asset URL) |
"videoupload" | Video upload picker | string (uploaded asset URL) |
"actionbutton" | Clickable action button | none β fires the configAction event |
Font Pickerβ
The font picker type will load Google fonts on the fly that allows a user to select their font. Just make sure you use the variable in the CSS.
Example:
Configs Tab
{
"font": {
"type": "fontpicker",
"label": "Font"
}
}
Data Tab
{
"font": "Impact"
}
Css Tab
body {
font-family: '{{font}}';
}
The key in the json an ddata must match the variable name used in the css
Note we're using variable replacement within the css.
Textareaβ
The textarea type renders a multi-line text box for long-form content. It supports the same placeholder, enableVariables, and allowedVariables props as input, plus a rows prop to control the visible height (defaults to 4).
Configs Tab
{
"welcomeMessage": {
"type": "textarea",
"label": "Welcome Message",
"rows": 6,
"placeholder": "Enter the message shown when the overlay loads",
"enableVariables": true,
"allowedVariables": ["username"]
}
}
The value flows into Overlay.data.welcomeMessage as a plain string and can be used with {{welcomeMessage}} replacement just like any other field.
Media Upload Fields (Image / Sound / Video)β
The imageupload, soundupload, and videoupload types render an asset picker tied to Lumia's media browser. Once a user picks (or uploads) an asset, the field's value is the uploaded asset's URL string. Each type scopes the picker to the matching media kind: images, audio, and video respectively.
Configs Tab
{
"logo": {
"type": "imageupload",
"label": "Logo Image"
},
"chime": {
"type": "soundupload",
"label": "Alert Sound",
"accept": "audio/mpeg,audio/ogg"
},
"intro": {
"type": "videoupload",
"label": "Intro Clip"
}
}
Because the value is just a URL, you can use it directly in your HTML, CSS, or JS:
<img src="{{logo}}" alt="logo" />
const chime = new Audio(Overlay.data.chime);
chime.play();
The optional
acceptproperty is a comma-separated MIME hint (e.g.image/png,image/jpeg). The picker is already scoped by media type, soacceptis an extra narrowing hint and can be omitted.
Action Buttonβ
The actionbutton type renders a clickable button in the Configs sidebar. Unlike every other field, it has no persisted value β it is a trigger, not a setting. Clicking it dispatches a configAction event that your overlay JS can subscribe to. Use buttonLabel to override the button text (it defaults to label).
Configs Tab
{
"resetGame": {
"type": "actionbutton",
"label": "Reset",
"buttonLabel": "Reset game"
}
}
JS Tab
// Fired when the user clicks an actionbutton field in the Configs sidebar.
// `key` is the field's JSON key; `value` is the field's optional `value`.
Overlay.on("configAction", ({ key, value }) => {
if (key === "resetGame") {
toast("Game reset!");
// run your reset logic here
}
});
configActionis a Configs-driven event, not a platform event β it does not need to be declared in the Data tab'seventsarray and is unaffected by the OverlayListener subscriptions.
Field Display Orderβ
By default, config fields are displayed in alphabetical order by their key names. You can override this behavior using the order property:
- Fields with an
orderproperty are displayed first, sorted by their order value (ascending) - Fields without an
orderproperty appear after all ordered fields, sorted alphabetically by key - This allows you to prioritize important settings at the top of the configuration panel
Order Exampleβ
{
"showStartupMessage": {
"order": 1,
"type": "checkbox",
"label": "Send a chatbot help message when the overlay loads",
"value": true
},
"allowModCommands": {
"order": 2,
"type": "checkbox",
"label": "Enable moderator runtime commands",
"value": true
},
"backgroundColor": {
"type": "colorpicker",
"label": "Background Color"
},
"textColor": {
"type": "colorpicker",
"label": "Text Color"
},
"font": {
"type": "fontpicker",
"label": "Font"
}
}
In this example, the fields would appear in this order:
showStartupMessage(order: 1)allowModCommands(order: 2)backgroundColor(no order, alphabetically first)textColor(no order, alphabetically second)
Sample Config fieldsβ
{
"name": {
"order": 1,
"type": "input",
"label": "Name:"
},
"last": {
"order": 2,
"type": "input",
"label": "Last Name:",
"value": "Abc"
},
"partner": {
"order": 3,
"type": "checkbox",
"label": "Partner:",
"value": false
},
"age": {
"order": 4,
"type": "number",
"label": "Age:",
"min": 0,
"max": 100,
"value": 18
},
"color": {
"order": 5,
"type": "colorpicker",
"label": "Favorite Color:"
},
"subcolor": {
"order": 6,
"type": "colorpicker",
"label": "Sub-color:",
"visibleIf": {
"key": "color",
"equals": "blue"
}
},
"platform": {
"order": 7,
"type": "dropdown",
"label": "Platform:",
"options": {
"twitch": "Twitch",
"youtube": "Youtube",
"kick": "Kick"
}
},
"multiplatforms": {
"order": 8,
"type": "multiselect",
"label": "Multi Streaming Platforms:",
"options": {
"twitch": "Twitch",
"youtube": "Youtube",
"kick": "Kick",
"tiktok": "Tiktok"
}
},
"font": {
"order": 9,
"type": "fontpicker",
"label": "Font:"
},
"fontSize": {
"order": 10,
"type": "slider",
"label": "Font Size:",
"options": {
"step": 10,
"min": 0,
"max": 200,
"prefix": "",
"suffix": "px"
}
},
"bio": {
"order": 11,
"type": "textarea",
"label": "Bio:",
"rows": 4,
"placeholder": "Tell us about yourself"
},
"logo": {
"order": 12,
"type": "imageupload",
"label": "Logo:"
},
"reset": {
"order": 13,
"type": "actionbutton",
"label": "Reset",
"buttonLabel": "Reset overlay"
}
}
Visible If Conditional Fieldsβ
The visibleIf field is a conditional statement that determines whether a field should be visible or not based on a condition.
The visibleIf field can be either:
- a single rule object with two keys,
keyandequals, or - an array of rule objects, in which case the field is shown only when every rule matches (logical AND). Use the array form to gate a field on more than one key at once.
The key key is a string that represents the name of the field whose value should be used in the conditional statement.
The equals key is a string, number, boolean, or an array that represents the value(s) of that field that make the condition match. When equals is an array, the rule matches if the field's value is one of the listed values (and for multiselect fields, if any selected value is in the list).
Single Primitive Equals Exampleβ
{
"hasAlerts": {
"type": "checkbox",
"label": "Show alerts"
},
"title": {
"type": "input",
"label": "Title",
"visibleIf": {
"key": "hasAlerts",
"equals": true
}
}
}
Multiple Primitive Equals Exampleβ
{
"color": {
"type": "input",
"label": "Colors"
},
"subcolor": {
"type": "input",
"label": "Subcolors",
"visibleIf": {
"key": "color",
"equals": ["red", "blue", "green"]
}
}
}
Multiple Keys (Array of Rules) Exampleβ
Pass an array of rule objects to require that every rule matches before the
field shows. Here subcolor only appears when both color and alerts
are set to one of red, blue, or green:
{
"color": {
"type": "input",
"label": "Colors"
},
"subcolor": {
"type": "input",
"label": "Subcolors",
"visibleIf": [
{
"key": "color",
"equals": ["red", "blue", "green"]
},
{
"key": "alerts",
"equals": ["red", "blue", "green"]
}
]
}
}
π Data Tabβ
The data fields are the current values that the user has selected. These can have default values by adding them in to the initial Data Tab. This data is passed to your Javascript code that can be accessed under Overlay.data.
Data Tab Example
{
"userSelectedColor": "#FF00FF"
}
Then in your JS Tab you can access the color with Overlay.data.userSelectedColor
Or you can even just use it directly with variable replacement using double curly braces
JS Tab
const userColor = Overlay.data.userSelectedColor;
console.log('User Color is', userColor);
// Or use it with variable replacing
const myUserColor = '{{userSelectedColor}}';
Module Flavorsβ
Custom overlay modules carry an optional content.flavor field that tells the runtime which compatibility layer (if any) to load before executing your HTML/CSS/JS. Most overlays you write from scratch don't set this β the field is mainly used by automated import flows.
flavor value | Set by | What the runtime does |
|---|---|---|
| undefined / missing | Hand-authored Lumia custom overlays | Runs your JS directly with the Lumia Overlay.* API available on window.Overlay. Use this by default. |
'streamelements' | StreamElements import wizard | Loads the SE compatibility shim before your JS runs: jQuery, Lodash, GSAP, a SE_API polyfill with store/counters, the fieldData object, the SE event reshape (onWidgetLoad / onEventReceived listeners), and listener-name aliasing so widgets coded against subscriber-latest etc. still fire. Useful for "I dragged in an SE widget and I want it to keep working as I tune it over." |
'ai-generated' | The wizard's "Generate with AI" step | Plain Lumia runtime (same as undefined), tagged so the editor can label the layer as AI-authored in disclosures. No runtime difference from the default flavor β purely a provenance marker. |
If you're inspecting an imported overlay's JSON and see "flavor": "streamelements", that's why it can call SE_API.store.set(...), jQuery, etc. β none of those are part of the native Lumia overlay API, but the shim makes them available for that specific layer.
StreamElements imports also stamp a content.sourceProvider field ('twitch', 'youtube', or 'kick', defaulting to 'twitch'). The SE compatibility shim reads it to pick the correct listener-name mapping when forwarding Lumia events into the iframe as onEventReceived payloads. Hand-authored overlays don't set it.
When the import wizard translates an SE widget's fields into Lumia config fields, it maps SE control types onto the native Lumia field types β googleFont β fontpicker, image / image-input β imageupload, sound-input β soundupload, video-input β videoupload, button β actionbutton, and SE hidden fields onto a plain input carrying hidden: true. The emitted type is always a native ConfigsFieldType, so an imported overlay's Configs tab behaves identically to one you author by hand.
Code IDβ
The codeId is primarily meant to be used when talking to Lumia Stream. It is used to store storage data, it is used when calling send overlay content that will only send to overlays with that specific code id. It can be retrieved within the overlay using Overlay.on('overlaycontent', (data) => {...})
The codeId can only contain letters, numbers, hyphens, and underscores with a max of 25 characters.
β Use Casesβ
- Custom stream overlays for Twitch, YouTube, or other platforms
- Real-time dashboards for alerts and interactions
- Interactive visuals triggered by chat or external events
- Pokemon Catching Mini Game
- Duel Overlay to show matches on stream with your viewers
Persisting Dataβ
The recommended way to persist data in your custom overlay is by using the provided Overlay.saveStorage and Overlay.setVariable methods. These allow you to store data that is scoped to your overlay's codeId and can be accessed across sessions, without relying on browser localStorage or global Lumia Stream variables.
Here's an example using Overlay.saveStorage to persist a counter:
const countEl = document.getElementById('count');
let counter = 0;
load();
// We need an async function when loading since getStorage is asynchronous
async function load() {
counter = await Overlay.getStorage('counter');
render();
}
/* --- helper -----------------------------------------------------*/
async function render() {
countEl.textContent = counter;
await Overlay.saveStorage('counter', counter); // persist
}
/* --- listen only to chat ---------------------------------------*/
Overlay.on('chat', (data) => {
const username = data.username;
const avatar = data.avatar;
const message = data.message;
if (message === '!count') {
counter++;
render();
}
toast(`New chat message received from ${username}. They said ${message}`);
});
Showing variables in your codeβ
You can display the value of a variable or storage item in your overlay by directly accessing it the same way as you would do in a chatbot message, using double curly braces {{myvar}}. We will replace the variable in your HTML, CSS, and JS every time the variable changes. Just make sure it's a variable that exist in Lumia when the overlay loads as it will only update variables that exist. If you need to create a variable dynamically, create it in the overlay but then prompt to reload after the initial load. You can check to see if the variable exists within JS.
Variables can be used in these examples
In CSS, leave color/size/number variables unquoted. In JS, put replacement tokens inside quotes and parse numbers manually when needed.
HTML Tab
<div>{{myvar}}</div>
CSS Tab
div {
color: {{myvarcolor}};
}
JS Tab
const myvar = '{{myvar}}';
Using variables from data tabβ
You can also use variables in the same way as Lumia variables from your data tab.
So if you wanted to allow changing of your background color you would set it up like this:
Configs Tab
{
"background": {
"type": "input",
"label": "background",
"value": "blue"
}
}
Data Tab
{
"background": "blue"
}
CSS Tab
body {
background: {{background}};
}
You can also use the same data variables in your JS, HTML, and CSS Tabs
JS Tab
const myBg = '{{background}}';
HTML Tab
<div>User selected background {{background}}</div>
π€ Sending Data from Lumia Streamβ
Lumia Stream allows you to send data from your application to the overlay. This can be used to allow communication from Lumia Stream without needing variables. The way to do this is through either the Send Custom Overlay Content Overlay action or by using the overlaySendCustomContent action in your Custom Javascript code within a command. When using Send Custom Overlay Content Overlay action or overlaySendCustomContent it is required that you include the codeId otherwise there is no way the overlay will receive your content. The overlay will always only send to custom overlay layers with your codeId. An example of this would be in a command custom javascript code send:
async function() {
overlaySendCustomContent({
codeId: "mycode",
content: JSON.stringify({ type: "add", value: "{{username}} - {{message}}" })
});
// Make sure you call done() to avoid memory leaks
done();
}
Then in your Custom Overlay JS Tab you would listen to it and parse it with:
Overlay.on("overlaycontent", (data) => {
const content = JSON.parse(data.content);
toast(`This is for my code: ${content.value}`);
});
Using JS Game Enginesβ
Our Overlays will work with various Game Engines including, but not limited to Phaser.js, Pixi.js, Three.js, and more.
π§ͺ Tipsβ
- Always sanitize HTML content if displaying user-generated input.
- Leverage custom CSS to match your stream or brand style.
- In custom code or overlay actions prefer to send data directly to the overlay without the store or variables if you do not need to persist the data
π¦ Templatesβ
You can get started quickly with one of the two built-in templates:
- Custom Chatbox β A base template for building a chatbox overlay that listens to messages.
- Custom Alert β A template for creating a fully custom alert layer that responds to selected events.
Using API requests with Fetchβ
You can 100% use fetch api requests from within Overlays to call any API you need. Here is an example:
const url = `https://api.adviceslip.com/advice`;
const slip = await fetch(url).then((r) => r.json());
const advice = slip.slip?.advice || 'Take life one step at a time.';