Repository navigation
Developer Guide
This guide covers creating custom modules, the schema system, and how the editor discovers and renders module settings.
www/modules/mymodule/
├── info.json # Metadata, schema, entrypoint
└── mymodule.js # Module code
{
"name": "mymodule",
"displayName": "My Module",
"icon": "🔮",
"type": "canvas",
"configKey": "mymodule",
"entrypoint": "mymodule.js",
"hasSettings": true,
"allowMultiple": false,
"description": "Does something cool",
"gradient": { "from": "rgba(100, 200, 50, 0.08)", "to": "rgba(100, 200, 50, 0.25)" },
"schema": {
"_type": {
"speed": "number",
"enabled": "bool",
"color": "color"
}
}
}if (!window.MyModule) {
window.MyModule = class MyModule {
constructor() {
// Initialise your module
}
draw(ctx, settings, area) {
if (!area) return;
// Draw within the area bounds
ctx.fillStyle = settings?.color || '#ffffff';
ctx.fillRect(area.x, area.y, area.width, area.height);
}
update(dt) {
// Called every frame, dt in seconds
}
onMessage(data) {
// Handle WebSocket messages from Streamer.bot
}
};
if (document.getElementById('canvas')) {
const instance = new window.MyModule();
window.Modules.push({
name: "mymodule",
draw: (ctx, settings, area) => instance.draw(ctx, settings, area),
update: (dt) => instance.update(dt),
message: (data) => instance.onMessage(data)
});
}
} // end if (!window.MyModule)Add your module to www/modules/modules.json:
{
"chat": "chat/info.json",
"emote": "emote/info.json",
"mymodule": "mymodule/info.json",
"scene": "scene.js"
}Add "mymodule" to the Modules array in www/config.js:
Modules: ["emote", "chat", "mymodule", "scene"]The scene module should always be last.
| Field | Type | Description |
|---|---|---|
name |
string | Internal identifier, matches config keys |
displayName |
string | Shown in the editor palette |
icon |
string | Emoji for palette and layer list |
type |
string | Always "canvas"
|
configKey |
string | Key in Config where global settings live |
entrypoint |
string | JS filename to load (relative to module dir) |
hasSettings |
bool | Whether a settings tab appears in Global Settings |
allowMultiple |
bool | Whether multiple instances can exist per scene |
description |
string | Tooltip in the palette |
gradient |
object |
{ from, to } — editor highlight colours |
schema |
object | Settings schema for the editor UI |
The schema defines what controls the editor renders for your module's settings. It replaces the old _type / _item_type metadata that used to live inside the config.
"schema": {
"_type": {
"name": "string",
"count": "number",
"enabled": "bool",
"color": "color",
"device": "audioDevice"
}
}| Type | Renders |
|---|---|
"string" |
Text input |
"number" |
Number input |
"bool" |
Checkbox |
"color" |
Color picker swatch |
"audioDevice" |
Dropdown of audio input devices |
"gradient" |
Gradient editor with stops and preview |
"direction": {
"type": "select",
"options": ["left-right", "right-left", "top-down", "bottom-up"]
}Show a field only when another field has a specific value:
"level1": { "type": "color", "showWhen": { "field": "mode", "value": "levels" } },
"gradient": { "type": "gradient", "showWhen": { "field": "mode", "value": "gradient" } }When mode changes, the UI re-renders and shows/hides fields accordingly.
Use _item_type to define how sub-objects render:
"schema": {
"_type": {
"enabled": "bool"
},
"_item_type": {
"colors": "object",
"style": "css"
},
"colors": {
"_type": {
"primary": "color",
"secondary": "color"
}
}
}| Item Type | Renders |
|---|---|
"object" |
Collapsible group, rendered recursively |
"css" |
Key-value CSS property editor |
Sub-objects can have their own _type and _item_type:
"schema": {
"_item_type": {
"ChatBoxes": "object"
},
"ChatBoxes": {
"_type": {
"ShowBadges": "bool",
"position": { "type": "select", "options": ["top", "bottom"] }
},
"_item_type": {
"style": "css"
}
}
}The scene system calls your module's draw function with:
draw(ctx, settings, area)| Parameter | Description |
|---|---|
ctx |
Canvas 2D rendering context |
settings |
The instance's settings from the scene config (e.g. { src: "/media/bg.png", opacity: 1 }) |
area |
{ x, y, width, height } — where to draw on the canvas |
For modules with allowMultiple: true, draw is called once per instance with different settings/area each time.
Each module instance in a scene has:
"my_overlay": {
"_type": "image",
"area": { "x": 100, "y": 50, "width": 400, "height": 300 },
"settings": { "src": "/media/overlay.png", "opacity": 0.8 }
}-
_type— which module renders this instance -
area— pixel position and size -
settings— passed to the module'sdrawfunction
The key ("my_overlay") is the user-defined ID shown in the editor.
If your module has "hasSettings": true in info.json, it automatically gets a tab in the Global Settings panel. The tab renders your schema using the TypeRenderer, reading/writing to Config[configKey].
| Path | Purpose |
|---|---|
www/config.js |
User's config (gitignored) |
www/config.example.js |
Template for new installs |
www/modules/modules.json |
Master manifest |
www/modules/{name}/info.json |
Module metadata + schema |
www/modules/{name}/{entrypoint} |
Module code |
www/modules/global.info.json |
Schema for root config fields |
www/media/ |
User-uploaded media (gitignored) |
editor/src/main/ |
Electron main process |
www/lib/ |
Shared libraries (wsclient, livereload, etc.) |
Messages from Streamer.bot arrive via wsclient.js and are routed by module name:
{
"data": {
"Module": "chat",
"Data": {
"Type": "MessageAdded",
"DisplayName": "User",
"Message": "Hello!",
...
}
}
}The Module field matches the module's registered name. The Data object is passed to the module's message(data) function.
The editor's server also has a WebSocket for:
-
{ type: "config-reload" }— triggers page reload in overlay -
{ type: "module-message", module: "chat", data: {...} }— sends a message to a specific module -
{ type: "raw", data: {...} }— broadcasts to all modules
Use the 📡 WebSocket Admin tool in the editor to test messages.