Project Structure
A Lumio extension project has this layout:
my-extension/
+-- lumio.config.json <- Extension manifest (required)
+-- package.json <- Dependencies, scripts (dev/build/deploy/typecheck/lint)
+-- tsconfig.json <- TypeScript config
+-- eslint.config.mjs <- ESLint config (double quotes enforced)
+-- CHANGELOG.md <- Versioned release notes (created by lumio init)
+-- src/
| +-- globals.css <- Tailwind CSS entry (@import "tailwindcss") or plain CSS
| +-- editor.tsx <- Dashboard panel (sidebar in overlay editor)
| +-- layer.tsx <- Stream overlay surface (transparent)
| +-- assets/ <- Static assets (images, fonts, video, audio, Lottie)
| +-- logo.png
| +-- animation.lottie
+-- server/ <- Optional (server: true in config)
+-- schema.ts <- DB schema (defineTable with validators)
+-- functions.ts <- Server functions (query/mutation/action)
During lumio init, you choose your styling approach: Tailwind CSS v4 (recommended), plain CSS, or inline styles. Tailwind extensions get src/globals.css with @import "tailwindcss" and tailwindcss as a dev dependency. See lumio build for details.
lumio.config.json
The manifest file that defines your extension. See Configuration for all fields.
{
"$schema": "https://lumio.vision/schemas/lumio.config.schema.json",
"extensionId": "88b2478a-39c7-468e-bebd-14e52fd97ac0",
"name": "My Extension",
"description": "What this extension does",
"category": "widget",
"version": "1.0.0",
"targets": ["editor", "layer"],
"server": false,
"permissions": [],
"visibility": "private",
"pricing": {
"type": "free"
}
}
The extensionId is generated when you run lumio init or create the extension in the dashboard. It must be a valid UUID that exists in the Lumio database.
src/editor.tsx
The editor surface renders in the dashboard overlay editor sidebar. This is where users configure your extension. Every editor file must call Lumio.render() within 10 seconds of page load.
src/layer.tsx
The layer surface renders in Browser Sources as a transparent stream overlay. Input components (buttons, toggles, text fields) are automatically read-only on this surface.
Lumio has no viewer page. Viewers interact with an extension through chat commands and channel point rewards handled by a bot module - see Viewer Input.
server/schema.ts
Defines database tables for your extension using defineSchema and defineTable. Each table gets isolated storage in ext_\{extension_id\}.* PostgreSQL schemas.
server/functions.ts
Defines server functions that run on the Lumio API. Declarative functions (queryRows, insertRow, patchRow, deleteRow) transpile to SQL. Handler-based functions (query(), mutation(), action()) run in a V8 sandbox.
CHANGELOG.md
lumio init creates this file with a versioned entry. Keep it in Keep a Changelog format, using a ## [1.2.0] - YYYY-MM-DD heading for each release. lumio deploy reads the entry for the version being uploaded as its release notes; -d overrides the uploaded notes.
For third-party extensions, the CLI can deploy without a changelog. If the file exists but lacks the deployed version, a local deploy needs -d or a matching entry; CI can upload without notes. Lumio's system extensions require a non-empty entry for the deployed version even in CI and even when -d is supplied. See lumio deploy.