# Lumio Extension Platform > Build widgets, overlays, bot modules, integrations, and themes for the > Lumio streaming platform. Extensions run sandboxed in iframes, communicate > via @zaflun/lumio-protocol, and can persist data through server functions. > > npm packages: @zaflun/lumio-sdk (components + hooks), @zaflun/lumio-cli > (dev toolchain), @zaflun/lumio-protocol (wire format), > @zaflun/lumio-extension-types (TypeScript types) --- ## Lumio Extension Platform # Lumio Extension Platform Build widgets, overlays, bot modules, integrations, and themes for the Lumio streaming platform. Extensions run sandboxed in iframes, communicate via `@zaflun/lumio-protocol`, and can persist data through server functions. ## What you can build | Category | Description | Example | |----------|-------------|---------| | **Widget** | Standalone visual element for stream overlays | Sports scoreboard, countdown timer | | **Overlay** | Full-screen transparent layer for streams | Custom alert animations | | **Bot Module** | Server-side chat command handler | Trivia game, loyalty points | | **Integration** | External service connector | Discord webhook relay | | **Theme** | Visual style pack for overlays | Neon cyberpunk theme | ## Architecture ```text Developer Workflow: lumio login -> lumio init -> lumio dev -> lumio build -> lumio deploy Runtime: Extension Bundle (HTML/JS/CSS) -> iframe Sandbox in Overlay/Dashboard Extension Server Functions -> Lumio API -> Isolated DB + Redis Distribution: Extension Store (Browse, Install, Rate, Purchase) Developer Dashboard (Analytics, Revenue, Payouts) ``` ## Quick links - [Becoming a Developer](/getting-started/becoming-a-developer) — Apply for developer access - [Quickstart](/getting-started/quickstart) — Hello World in 5 minutes - [SDK Reference](/api-reference/sdk-client) — All hooks and components - [CLI Reference](/api-reference/cli) — All CLI commands - [Teams](/teams/overview) — Collaborate with other developers - [Examples](/examples/hello-world) — Working code examples - [AI Assistant Integration](/guides/ai-assistant) — Use llms.txt with your AI coding assistant - [llms.txt](pathname:///llms.txt) — Structured documentation index for LLMs - [llms-full.txt](pathname:///llms-full.txt) — Full documentation in a single file --- ## Getting Started / Overview # Overview Lumio Extensions are sandboxed applications that run inside the Lumio platform. They use React for UI, communicate with the host via `postMessage`, and can define server-side functions for data persistence and external API calls. ## Three surfaces Every extension can render on up to three surfaces: | Surface | File | Where it renders | Purpose | |---------|------|-----------------|---------| | **Editor** | `src/editor.tsx` | Dashboard overlay editor sidebar | Configuration UI, settings panels | | **Layer** | `src/layer.tsx` | Browser Source (transparent) | Visual overlay in the stream | | **Interactive** | `src/interactive.tsx` | Standalone page (`/ext/{slug}`) | Audience interaction (votes, games) | All three surfaces share the same storage via `useExtensionStorage()` — a change in the editor propagates to the layer in real time. ## Server functions Extensions can optionally define server-side functions that run on the Lumio API: - **Declarative** — `queryRows`, `insertRow`, `patchRow`, `deleteRow` transpile directly to SQL (no JS runtime overhead) - **Handler-based** — `query()`, `mutation()`, `action()` run in a V8 isolate sandbox with memory and CPU limits Server functions get their own isolated PostgreSQL schema (`ext_{extension_id}`) and Redis namespace (`lumio:ext:{extension_id}:{account_id}:*`). ## Security model Extensions are sandboxed at 9 layers: admin code review, DB isolation, iframe sandbox, egress allowlist, rate limiting, V8 sandbox, token scoping, Redis isolation, and secret isolation. See [Security](/account/security) for details. ## SDK The `@zaflun/lumio-sdk` package provides React hooks and components: - **14 components** — layout, input, and display primitives - **10 hooks** — storage, events, actions, identity, config, theme, queries, mutations - **Server exports** — schema definitions, declarative CRUD, handler-based functions ## Distribution Extensions are distributed through the Lumio Extension Store. The publishing workflow is: `lumio deploy` (draft) → submission wizard → admin review → testing phase → publish. --- ## Getting Started / Becoming a Developer # Becoming a Developer To build and publish extensions on Lumio, you need an approved developer profile. This page walks through the application process. ## Prerequisites - A Lumio account (sign up at [lumio.vision](https://lumio.vision)) - A personal profile (not an account/team profile) ## Applying for developer access ### 1. Navigate to the developer page Log in to [lumio.vision](https://lumio.vision) and switch to your **Personal Profile** if you are currently viewing an account. Open **Account > Developer** from the sidebar. ### 2. Open the application wizard Click **Apply as Developer** to launch the 4-step wizard. If a previous application was rejected, you see the rejection reason and a **Reapply** button instead. ### 3. Complete the wizard #### Step 1: Type Choose between two developer modes: | Type | Description | |------|-------------| | **Solo** | You develop extensions independently under your own profile | | **Team** | You plan to collaborate with others. A team is created alongside your developer profile | You can always create or join additional teams later, regardless of which type you choose here. #### Step 2: Profile Fill in your developer identity: | Field | Required | Description | |-------|----------|-------------| | **Display Name** | Yes | Public name shown on the Extension Store | | **Slug** | Yes | URL-safe identifier (3-50 characters, lowercase letters, numbers, hyphens). Auto-generated from display name, editable. Appears in URLs like `lumio.vision/store/developers/{slug}` | | **Description** | Yes | Short bio describing you or your project | | **Motivation** | Yes | Why you want to build on Lumio | | **What to Build** | Yes | What extensions you plan to create | #### Step 3: Links (optional) Provide additional context to help reviewers evaluate your application: | Field | Description | |-------|-------------| | **GitHub URL** | Link to your GitHub profile or a relevant repository | | **Website URL** | Personal or project website | | **Experience** | Free-text description of your development experience | All fields in this step are optional, but filling them in strengthens your application. #### Step 4: Review A summary of everything you entered. Verify the details are correct, then click **Submit Application**. ### 4. Wait for approval After submission, your application enters the **pending** state. The Lumio team reviews applications manually. You receive an email notification when your application is approved or rejected. While pending, the developer page shows a summary card with your submitted information and a "pending" badge. ## After approval Once approved: - The **Extensions** sidebar section appears in your dashboard - You can run `lumio login` and `lumio init` in the CLI to scaffold your first extension - You can create and manage [developer teams](/teams/overview) - The developer page shows a green "approved" card with a link to the extensions dashboard ## If rejected If your application is rejected, the developer page shows the rejection reason provided by the reviewer. You can update your application and reapply by clicking **Reapply**, which opens the wizard with your previous answers pre-filled. ## Next steps - [Quickstart](/getting-started/quickstart) -- build your first extension in 5 minutes - [Teams](/teams/overview) -- collaborate with other developers - [Prerequisites](/getting-started/prerequisites) -- tools and knowledge needed --- ## Getting Started / Quickstart # Quickstart Build and run your first Lumio extension in 5 minutes. ## 0. Apply as a developer Before using the CLI, you need an approved developer profile. If you have not applied yet, follow the steps in [Becoming a Developer](/getting-started/becoming-a-developer) to submit your application and wait for approval. The `lumio init` command requires an approved developer profile to create extensions. ## 1. Install the CLI ```bash npm install -g @zaflun/lumio-cli ``` ## 2. Log in to Lumio ```bash lumio login ``` This opens your browser for PKCE OAuth authentication. After login, credentials are stored in `~/.lumio/credentials`. For non-interactive environments, the CLI can also store a pre-provisioned Lumio API token: ```bash lumio login --token lm_usr_your_api_key_here ``` ## 3. Scaffold a new extension ```bash lumio init my-first-extension ``` The interactive wizard asks: ```text ? Extension name: My First Extension ? Description: A hello world extension ? Category: widget ? Targets: editor, layer ? Server functions: no ``` This creates the extension on Lumio (generating an Extension-ID), scaffolds the project, and writes `lumio.config.json`. ## 4. Set up AI assistant (optional) If you use an AI coding assistant (Claude Code, Copilot, Cursor, Gemini, Windsurf, Codex), install the Lumio skill files so your assistant understands the SDK: ```bash npx @zaflun/lumio-agent-skill add ``` This creates `.lumio/skills/` with reference docs that teach your assistant about components, hooks, server functions, and CLI commands. See [AI Assistant Integration](/guides/ai-assistant) for per-tool setup. ## 5. Start the dev server ```bash cd my-first-extension pnpm install lumio dev ``` This starts a Vite dev server with hot-reload and a Lumio sandbox that simulates the host environment with mock events. ## 6. Edit the extension Open `src/editor.tsx`: ```tsx import { Lumio, CompactView, TextField, Toggle, useExtensionStorage } from "@zaflun/lumio-sdk"; function Settings() { const [storage, setStorage] = useExtensionStorage(); return ( setStorage({ ...storage, visible: checked })} /> setStorage({ ...storage, text: value })} /> ); } Lumio.render(, { target: "editor" }); ``` Open `src/layer.tsx`: ```tsx import { Lumio, Text, useExtensionStorage } from "@zaflun/lumio-sdk"; function Overlay() { const [storage] = useExtensionStorage(); if (!storage.visible) return null; return ; } Lumio.render(, { target: "layer" }); ``` Changes in the editor surface propagate to the layer in real time. ## 7. Build and deploy ```bash lumio build lumio deploy -v 1.0.0 -d "Initial release" ``` Your extension is now uploaded as a draft. Use the [submission wizard](/publishing/submission-wizard) in the dashboard to submit it for review. > **AI-Assisted Development:** > Building with an AI coding assistant? Feed it the full Lumio documentation in one go: > > - **[llms.txt](https://developers.lumio.vision/llms.txt)** — structured index of all documentation pages > - **[llms-full.txt](https://developers.lumio.vision/llms-full.txt)** — complete documentation in a single file > > Paste the URL or content into your assistant's context for accurate Lumio extension help. > For deeper integration, install the [`@zaflun/lumio-agent-skill`](https://www.npmjs.com/package/@zaflun/lumio-agent-skill) package. ## Next steps - [Project structure](/getting-started/project-structure) — understand every file - [Surfaces](/surfaces/overview) — learn about the three rendering targets - [SDK hooks](/api-reference/sdk-client) — all available hooks and components - [Server functions](/server-functions/overview) — add data persistence --- ## Getting Started / Prerequisites # Prerequisites ## Required | Requirement | Minimum Version | Notes | |-------------|----------------|-------| | **Node.js** | 22.13+ | LTS recommended. Check with `node --version` | | **pnpm** or **npm** | pnpm 9+ / npm 10+ | pnpm recommended for workspace features | | **Lumio account** | — | Sign up at [lumio.vision](https://lumio.vision) | | **React** | 18.0+ or 19.0+ | Both major versions supported | | **TypeScript** | 5.0+ | Extensions are written in TypeScript + JSX | ## Recommended - **VS Code** with the TypeScript and ESLint extensions - **React Developer Tools** browser extension for debugging - **OBS Studio** for testing layer surfaces in a real streaming environment ## Knowledge You should be comfortable with: - React (functional components, hooks) - TypeScript (types, interfaces, generics) - Basic terminal usage (npm/pnpm, command line) No backend experience is needed for client-only extensions. For server functions, familiarity with database concepts (tables, queries) is helpful. --- ## Getting Started / Project Structure # Project Structure A Lumio extension project has this layout: ```text 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 <- Release notes (optional, recommended) +-- 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) | +-- interactive.tsx <- Audience page (optional) | +-- 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) ``` > **Styling:** > 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](/cli/build#css-and-styling) for details. ## `lumio.config.json` The manifest file that defines your extension. See [Configuration](/config/lumio-config) for all fields. ```json { "$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. ## `src/interactive.tsx` The interactive surface renders on a standalone page at `/ext/{slug}`. Viewers can interact with it directly (voting, playing games). This surface is optional. ## `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` Optional (recommended). If present, should follow the [Keep a Changelog](https://keepachangelog.com/) format. `lumio deploy` reads the entry for the deployed version and uses it as a fallback for release notes. The primary way to write release notes is the dashboard submission wizard. --- ## Surfaces / Surfaces Overview # 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` | 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`: ```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. ```tsx // Good: render immediately, load data inside the component function Settings() { const [storage] = useExtensionStorage(); // storage is available synchronously on first render return ...; } Lumio.render(, { target: "editor" }); // Bad: waiting for async work before rendering async function init() { await someAsyncSetup(); // may take too long! Lumio.render(, { 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](/surfaces/editor-panel), [Layer](/surfaces/layer), [Interactive Page](/surfaces/interactive-page), and [Shared Storage](/surfaces/shared-storage) for surface-specific documentation. --- ## Surfaces / Editor Panel # Editor Panel The editor surface renders in the dashboard overlay editor sidebar. It is the primary configuration UI for your extension — where streamers adjust settings, enter text, toggle features, and manage data. ## Entry point Every extension that includes the `"editor"` target must have a `src/editor.tsx` file that calls `Lumio.render()`: ```tsx // src/editor.tsx import { Lumio, CompactView, TextField, Toggle, useExtensionStorage } from "@zaflun/lumio-sdk"; function Settings() { const [storage, setStorage] = useExtensionStorage(); return ( setStorage({ ...storage, visible: checked })} /> setStorage({ ...storage, homeTeam: value })} /> setStorage({ ...storage, awayTeam: value })} /> ); } Lumio.render(, { target: "editor" }); ``` ## Layout constraints The editor panel renders inside a fixed-width sidebar (approximately **320px** wide). Follow these guidelines: - Use `CompactView` for sections — it provides a consistent heading and padding - Use `VStack` for vertical layouts within sections - Avoid horizontal scrolling — content must fit within 320px - Use `HStack` sparingly and only when the two children are very compact (e.g., a label + short input) - Keep labels concise — long labels wrap awkwardly in the narrow sidebar ## Multiple sections Use multiple `CompactView` components for logical groups of settings: ```tsx import { Lumio, CompactView, VStack, TextField, NumberField, Dropdown, Toggle, useExtensionStorage } from "@zaflun/lumio-sdk"; function Settings() { const [storage, setStorage] = useExtensionStorage(); return ( setStorage({ ...storage, homeTeam: value })} /> setStorage({ ...storage, awayTeam: value })} /> setStorage({ ...storage, position: value })} /> setStorage({ ...storage, opacity: value })} /> ); } Lumio.render(, { target: "editor" }); ``` ## Configuration approaches Three valid approaches exist for building extension settings: ### 1. `config_schema` only (no `editor.tsx`) Define a `config_schema` in `lumio.config.json` and the host auto-generates a settings panel from the schema. No custom code needed -- the host renders each field using the same `@lumio/ui` components your `editor.tsx` would produce. ```json { "config_schema": { "primaryColor": { "type": "color", "label": "Primary color", "default": "#6366f1" }, "fontSize": { "type": "slider", "label": "Font size", "default": 16, "min": 8, "max": 72, "unit": "px" } } } ``` This is the recommended approach for simple settings. See [config_schema](/config/lumio-config#config_schema) for the 17 supported field types. ### 2. `editor.tsx` only (no `config_schema`) Build a fully custom settings UI using SDK components. Use this when you need dynamic behavior, conditional fields, server function calls, or layouts that go beyond what the schema can express. ### 3. Both `config_schema` and `editor.tsx` When both are present, the auto-generated schema fields render first, followed by the custom editor panel below. This lets you define simple settings declaratively in the schema while adding custom interactive elements in `editor.tsx`. ### Config schema field types The following 17 field types are supported in `config_schema`: | Type | Description | Key properties | |------|-------------|----------------| | `string` | Single-line text input | `placeholder` | | `number` | Numeric input | `min`, `max`, `step` | | `boolean` | Toggle switch | | | `color` | Color picker with hex input | | | `select` | Dropdown with predefined options | `options` | | `textarea` | Multi-line text input | `placeholder`, `rows` | | `slider` | Range slider | `min`, `max`, `step`, `unit` | | `font` | Font family picker (Google Fonts) | | | `icon` | Icon picker (Lucide, emoji, emotes, uploads) | | | `image` | Image upload or URL | | | `sound` | Sound picker from the account's sound library | Includes extension-bundled sounds | | `multiselect` | Multiple selection from options | `options`, `multiple` | | `group` | Collapsible group of nested fields | `fields`, `collapsed` | | `divider` | Visual separator (no value) | | | `info` | Read-only informational text (no value) | `content` | | `channel` | Multi-platform channel picker | | | `designer` | Fullpage designer surface field | `presets` | ### `designer_schema` for fullpage design editors Extensions that include the `"designer"` target can define a `designer_schema` in `lumio.config.json`. The designer provides a split-pane view with a live preview alongside configuration fields. Fields use the same type system as `config_schema`, but the schema is an array (with explicit `key` properties) rather than an object: ```json { "targets": ["layer", "designer"], "designer_schema": [ { "key": "layout", "type": "select", "label": "Layout", "default": "horizontal", "options": [ { "value": "horizontal", "label": "Horizontal" }, { "value": "vertical", "label": "Vertical" } ] }, { "key": "bgColor", "type": "color", "label": "Background", "default": "#1a1a2e" } ] } ``` See [lumio.config.json reference](/config/lumio-config#designer_schema) and [Targets](/config/targets) for details. ### Using `useLumioConfig` in `editor.tsx` When building a custom editor, use the `useLumioConfig` hook with a functional updater to modify individual settings without overwriting the entire config object: ```tsx import { useLumioConfig, EditorInput, EditorSlider } from "@zaflun/lumio-sdk"; function Settings() { const [config, setConfig] = useLumioConfig(); // Functional updater -- recommended for editor.tsx const update = (key: string, value: unknown) => setConfig((prev) => ({ ...prev, settings: { ...(prev.settings ?? {}), [key]: value }, })); return ( <> update("title", v)} /> update("fontSize", v)} min={8} max={72} unit="px" /> ); } ``` The functional updater `(prev) => next` prevents race conditions when multiple fields update concurrently. ## Available components All SDK components work on the editor surface. Input components are **fully interactive** on this surface: | Component | Type | Works in editor | |-----------|------|-----------------| | `Box` | Layout | Yes | | `VStack` | Layout | Yes | | `HStack` | Layout | Yes | | `CompactView` | Layout | Yes | | `List` | Layout | Yes | | `Text` | Display | Yes | | `Image` | Display | Yes | | `Button` | Input | Yes (interactive) | | `Toggle` | Input | Yes (interactive) | | `TextField` | Input | Yes (interactive) | | `TextArea` | Input | Yes (interactive) | | `NumberField` | Input | Yes (interactive) | | `Dropdown` | Input | Yes (interactive) | | `Slider` | Input | Yes (interactive) | ### Editor-specific components The SDK provides 13 additional components designed specifically for editor panels. These serialize via the Worker Reconciler and render as `@lumio/ui` components on the host side: | Component | Description | |-----------|-------------| | `EditorInput` | Text, number, or color input with optional unit suffix | | `EditorTextarea` | Multi-line text input | | `EditorSelect` | Dropdown select with predefined options | | `EditorCheckbox` | Toggle checkbox | | `EditorSlider` | Range slider with optional unit | | `EditorColorPicker` | Color picker with hex input | | `EditorFontPicker` | Font family picker with weight and italic controls | | `EditorFontRow` | Compact font styling row (bold, italic, weight, size, color) | | `EditorButton` | Action button (primary, default, destructive variants) | | `EditorGroup` | Collapsible section group | | `EditorLabel` | Section label/heading | | `EditorDivider` | Visual separator | | `EditorInfo` | Informational text block | See [Editor Components](/sdk/components/editor) for the full API reference with props and examples. ## Calling server functions The editor can call server functions — both queries and mutations. Mutations with `editorOnly: true` can **only** be called from the editor surface; the layer and interactive surfaces cannot call them. ```tsx import { Lumio, CompactView, Button, List, Text, useQuery, useMutation } from "@zaflun/lumio-sdk"; function RuleManager() { const { data: rules, refetch } = useQuery("getRules"); const { mutate: addRule } = useMutation("addRule"); const { mutate: deleteRule } = useMutation("deleteRule"); return ( ); } ``` ## Example — build a sound picker in editor.tsx ```tsx import { useLumioConfig, useLumioSound, EditorSelect } from "@zaflun/lumio-sdk"; import { useEffect, useState } from "react"; function SoundEditor() { const [config, setConfig] = useLumioConfig(); const { getSounds } = useLumioSound(); const [sounds, setSounds] = useState([]); useEffect(() => { getSounds().then(setSounds); }, []); const options = [ { value: "", label: "None" }, ...sounds.map((s) => ({ value: s.id, label: s.name })), ]; return ( setConfig((prev) => ({ ...prev, alertSound: v || null }))} /> ); } ``` ## Required permissions The extension must declare `sounds:play` in `lumio.config.json` to use `playSound` and `stopSound`: ```json { "permissions": ["sounds:play"] } ``` `getSounds` does not require any declared permission — it is available to all extensions by default. ## Notes - `playSound` and `stopSound` broadcast to **all** browser sources in the account. There is no per-surface targeting from the SDK — use the REST or GraphQL API directly if you need to target a specific overlay key. - Sounds must exist in the account's library. If `soundId` does not exist, the server returns a 404 and the promise rejects. - The `feature:sounds` feature flag must be enabled for the account. If it is disabled, all three methods throw a `FEATURE_DISABLED` error. --- ## SDK / Hooks / useExtensionStorage # useExtensionStorage Read and write the extension's shared key-value storage. Changes are persisted to the database and broadcast to all other surfaces in real time via WebSocket. ## Signature ```tsx const [storage, setStorage] = useExtensionStorage(); ``` ## Parameters This hook takes no parameters. ## Return value Returns a tuple of two values: | Index | Type | Description | |-------|------|-------------| | `0` | `T` | Current storage object (defaults to `{}` on first load) | | `1` | `(storage: T) => void` | Setter function — replaces the entire storage object | ## Example ```tsx import { Lumio, CompactView, Toggle, TextField, useExtensionStorage } from "@zaflun/lumio-sdk"; interface MyStorage { visible: boolean; homeTeam: string; awayTeam: string; homeScore: number; awayScore: number; } function Settings() { const [storage, setStorage] = useExtensionStorage(); // Safely read with defaults const visible = storage.visible ?? true; const homeTeam = storage.homeTeam ?? ""; return ( setStorage({ ...storage, visible: checked })} /> setStorage({ ...storage, homeTeam: value })} /> ); } Lumio.render(, { target: "editor" }); ``` ## Reading in the layer The same storage is accessible in the layer surface: ```tsx import { Lumio, Box, Text, useExtensionStorage } from "@zaflun/lumio-sdk"; function Overlay() { const [storage] = useExtensionStorage(); if (!storage.visible) return null; return ( ); } Lumio.render(, { target: "layer" }); ``` ## Spread rule The setter **replaces** the entire storage object. Always spread the current value: ```tsx // Correct: preserves all other keys setStorage({ ...storage, visible: true }); // Wrong: deletes all other keys! setStorage({ visible: true }); ``` ## Real-time sync All surfaces receive storage updates instantly via WebSocket — there is no polling. When the editor calls `setStorage()`, the layer re-renders within milliseconds. ## Persistence Storage is persisted to PostgreSQL. It survives: - Browser Source reloads - Page refreshes on the interactive surface - Extension updates (the data schema is not versioned — ensure backward compatibility when changing field names) ## Size limit The storage object must serialize to under **64 KB** of JSON. For larger structured data, use [server functions](/server-functions/overview). ## Notes - The type parameter `T` is optional but strongly recommended - On first load, storage is `{}` — always use `?? defaultValue` when reading fields - `setStorage()` applies an optimistic update locally before confirming with the server - Storage is scoped per installation — different overlay installs of the same extension have independent storage objects --- ## SDK / Hooks / useLumioTheme # useLumioTheme Read the current Lumio theme (dark, light, or a custom theme). Use this to adapt your extension's appearance to match the streamer's dashboard preferences. ## Signature ```tsx const theme = useLumioTheme(); ``` ## Parameters This hook takes no parameters. ## Return value | Type | Description | |------|-------------| | `LumioTheme` | The current theme object | ### LumioTheme shape ```typescript interface LumioTheme { mode: "dark" | "light" | "custom"; colors: { background: string; // e.g. "#0f0f0f" foreground: string; // e.g. "#ffffff" primary: string; // e.g. "#6366f1" primaryForeground: string; // e.g. "#ffffff" muted: string; // e.g. "#374151" mutedForeground: string; // e.g. "#9ca3af" border: string; // e.g. "#1f2937" card: string; // e.g. "#111827" cardForeground: string; // e.g. "#f9fafb" }; } ``` ## Example ```tsx import { useLumioTheme, Box, Text } from "@zaflun/lumio-sdk"; function ThemeAwareCard() { const theme = useLumioTheme(); return ( ); } ``` ## Adapting to dark/light mode ```tsx import { useLumioTheme } from "@zaflun/lumio-sdk"; function AdaptiveComponent() { const theme = useLumioTheme(); const shadowColor = theme.mode === "dark" ? "rgba(0, 0, 0, 0.5)" : "rgba(0, 0, 0, 0.1)"; return ( ... ); } ``` ## Notes - The theme updates in real time if the user switches themes while the extension is open - On the layer surface (OBS), the theme reflects the dashboard theme setting — it does not react to viewer's OS dark/light mode preference - SDK components automatically use the current theme for their default styling — `useLumioTheme()` is for custom styling in `style` props --- ## SDK / Hooks / useIconPicker # useIconPicker Headless hook for building custom icon picker UIs. Manages dialog open/close state and selection. ## Signature ```tsx const picker = useIconPicker(options?); ``` ## Parameters | Prop | Type | Required | Description | |------|------|----------|-------------| | `sources` | `IconSource[]` | No | Enabled sources (default: all four) | | `onSelect` | `(icon: IconValue \| null) => void` | No | Called on selection or clear | ## Return value | Property | Type | Description | |----------|------|-------------| | `isOpen` | `boolean` | Whether the picker dialog is open | | `selected` | `IconValue \| null` | Currently selected icon | | `open` | `() => void` | Opens the picker dialog | | `close` | `() => void` | Closes without selecting | | `select` | `(icon: IconValue \| null) => void` | Confirms selection and closes | | `clear` | `() => void` | Clears selection to `null` | | `sources` | `IconSource[]` | Active source list | ## Example ```tsx import { Button, useIconPicker, IconDisplay } from "@zaflun/lumio-sdk"; function CustomPicker() { const { open, selected, clear, isOpen } = useIconPicker({ sources: ["lucide", "emoji"], onSelect: (icon) => console.log("Selected:", icon), }); return (