Skip to main content

Surfaces

Extensions can render on three surfaces. Each surface is an independent React app that calls Lumio.render() with a target parameter.

SurfaceTargetFileRendering contextInteractivity
Editor"editor"src/editor.tsxDashboard sidebarFull (all components)
Layer"layer"src/layer.tsxOBS Browser SourceRead-only (inputs disabled)
Interactive"interactive"src/interactive.tsxStandalone pageFull (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:

CombinationUse 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.