Surfaces
Extensions can render on three surfaces. Each surface is an independent React app that calls Lumio.render() with a target parameter.
| Surface | Target | File | Rendering context | Interactivity |
|---|---|---|---|---|
| Editor | "editor" | src/editor.tsx | Dashboard sidebar | Full (all components) |
| Layer | "layer" | src/layer.tsx | OBS Browser Source | Read-only (inputs disabled) |
| Interactive | "interactive" | src/interactive.tsx | Standalone page | Full (all components) |
Shared state
All three surfaces share useExtensionStorage(). When the editor writes a value, the layer receives it in real time via WebSocket (ext-storage:\{install_id\} channel). This means:
- A toggle in the editor immediately shows/hides an element in the layer
- A vote cast on the interactive page can update a counter on the layer in real time
- All surfaces always read the same storage values
Declaring targets
List the surfaces you use in lumio.config.json:
{
"targets": ["editor", "layer"]
}
Only listed targets are bundled and deployed. The interactive surface is optional — most extensions use only editor + layer.
Valid combinations:
| Combination | Use case |
|---|---|
["editor"] | Configuration-only panel with no visual output |
["layer"] | Display-only overlay with no settings UI |
["editor", "layer"] | Standard overlay with configuration panel (most common) |
["editor", "layer", "interactive"] | Full extension with viewer interaction |
["editor", "interactive"] | Audience interaction with editor config, no overlay |
Render timeout
Lumio.render() must be called within 10 seconds of page load. If the timeout expires, the extension is terminated and the user sees an error message. Avoid long async initialization before calling Lumio.render() — instead, render immediately with a loading state.
// Good: render immediately, load data inside the component
function Settings() {
const [storage] = useExtensionStorage();
// storage is available synchronously on first render
return <CompactView title="My Extension">...</CompactView>;
}
Lumio.render(<Settings />, { target: "editor" });
// Bad: waiting for async work before rendering
async function init() {
await someAsyncSetup(); // may take too long!
Lumio.render(<Settings />, { target: "editor" });
}
init();
No direct DOM access
Extensions must use only SDK components. The React reconciler serializes the component tree to a JSON intermediate representation and the Lumio host renders actual DOM elements in its iframe sandbox. Direct document.createElement() or document.getElementById() calls have no effect inside the sandbox.
Surface isolation
Each surface is rendered in its own iframe with:
- Separate JavaScript execution context
- No shared memory between surfaces
- Communication only through
useExtensionStorage()and server functions - Content Security Policy enforced by the host
See Editor Panel, OBS Layer, Interactive Page, and Shared Storage for surface-specific documentation.