Skip to main content

Extensions

Extensions add features to Lumio accounts: widgets for overlays, bot modules for chat moderation, automation nodes, themes, and integrations. They are discovered and installed from the Extension Store.

Visibility levels​

VisibilityDescription
systemBuilt-in extensions maintained by Lumio. Always available, cannot be uninstalled.
publicCommunity extensions published by verified developers. Anyone can install them.
paidCommunity extensions with a price. Purchase required to install.
unlistedNot shown in Store search. Installable via direct link.
privateHidden from public discovery. A published extension is installable only by an account with an access grant or an invited tester.

Developers can set private, unlisted, public, or paid. The system value and the system- slug prefix are reserved for Lumio's built-in extensions. Create and update requests reject other visibility values and developer slugs beginning with system- (case-insensitive). A developer- or team-owned extension that already carries system visibility or a system- slug is never treated as a built-in extension. The API logs a warning for each such extension at startup so an operator can review it; the check does not change the extension.

Installation​

REST and GraphQL install requests require a published extension. Public, paid, unlisted, and system extensions can pass the visibility check; private extensions additionally require an access grant for the installing account or a tester invitation for the acting user. Payment and account permissions still apply where required. An extension that does not pass this check returns 404 Extension not found. Revoking a private access grant also prevents reinstalling through that grant.

Buying a paid extension (POST /v1/extension-purchases) runs the same check before a Stripe Checkout session is created, and before the free-access shortcut installs. An account that cannot install the extension gets 404 Extension not found and is not sent to payment. When Stripe reports the completed payment, the API checks again before it installs the extension. If the check fails at that point, for example because the access grant was revoked after checkout, the payment is recorded but the extension is not installed. The same applies when the extension or its version is deleted or unpublished while the buyer is still on the Stripe page: the payment is recorded without the deleted extension or version and nothing is installed. In both cases Lumio refunds the full amount automatically, right when the payment is reported; a subscription is cancelled at once, without proration, and its first payment is refunded. The purchase then shows as refunded. Lumio also e-mails the buyer that the extension was no longer available and that the full amount was refunded, because Stripe sends its own receipt only when that is enabled in the Stripe Dashboard. A payment that is not installed because of a temporary error is not refunded automatically. A recorded purchase does not give access to a private extension. A paid checkout also requires the buyer's withdrawal-waiver declaration (accept_withdrawal_waiver: true, text B for one-time purchases, text C for subscriptions) and sends an order confirmation after payment; see Billing → Withdrawal waiver and order confirmation. Purchase is available only over REST: checkout ends in a redirect to Stripe and a signed webhook, so there is no GraphQL mutation for it.

Draft and testing versions can be inspected by their developer, but an extension must have published status before the install endpoint accepts it.

Installing an extension the account already has returns 409 Extension is already installed (GraphQL: Extension is already installed).

Uninstalling and reinstalling​

Uninstalling (DELETE /v1/extension-installs/{id}, GraphQL uninstallExtension) takes the extension off the account at once. Its widgets, overlay layers and bundled sounds are deleted. The install no longer appears in install lists, the store's installed badge, or install lookups. Its functions, actions and storage answer Extension install not found, and bots and workers stop running its handlers and triggers.

By default the extension's stored data is kept for 30 days and deleted afterwards; delete_data_immediately deletes it right away. Deleting the data only ever removes this account's data: an extension keeps one database schema for every account that installs it, so uninstalling deletes this account's rows from it, and the schema itself is removed only with the extension's last install. Every uninstall is recorded in the account's audit log as account:extension_uninstalled, including whether the data was deleted immediately. Installing the same extension again within those 30 days restores the install on the latest published version, with its saved configuration and stored data. Update settings start fresh (auto-update on, no pinned version), and widgets, overlay layers and sounds that were deleted at uninstall are not restored. Built-in system extensions cannot be uninstalled, and Lumio's automatic system-extension setup never restores an install that was uninstalled.

System extensions​

System extensions replace the former built-in plugin system. They provide the same functionality (chat moderation modules, overlay widgets) but run through the standard extension infrastructure (V8 isolates, Extension Supervisor iframes).

Built-in widgets​

ExtensionSlugDefault InstallDescription
Chat Boxsystem-chat-boxYesLive chat feed from connected platforms
Alert Boxsystem-alert-boxYesAnimated alerts for follows, subs, raids
Event Listsystem-event-listYesScrolling list of recent events
Textsystem-textYesLabels, titles, and text overlays
Imagesystem-imageYesUpload or URL-based image overlays
Timersystem-timerYesCountdown / stopwatch timer
Spotify Now Playingsystem-spotify-now-playingNoDisplays the currently playing Spotify track
Browser Sourcesystem-obs-browser-sourceNoHeadless OBS integration events

Built-in bot modules​

ExtensionSlugDefault InstallDescription
Link Protectionsystem-link-protectionYesBlock or filter URLs in chat
Spam Protectionsystem-spam-protectionYesDetect excessive caps, symbols, emote spam
Word Filtersystem-word-filterYesBlock banned words, phrases, or regex patterns
Timed Messagessystem-timed-messagesYesSend messages on a rotating schedule

Properties​

  • visibility = 'system'
  • No developer or team owner (developer_id and team_id are NULL)
  • System-extension lookups require both owner fields to be NULL as well as the reserved slug, so developer-owned extensions are not selected as built-ins.
  • Always free (pricing_type = 'free')
  • Cannot be uninstalled (can be disabled via config)
  • Cannot be deleted by users or developers
  • 10 of 12 are auto-installed for new accounts on creation
  • lumio.config.json is the single source of truth - the API reads each manifest at compile time via include_str! (no hardcoded Rust structs)
  • All manifests include an icon field (Lucide icon name)
  • In local dev, bundles are uploaded from the filesystem; in production, deployed via CI (lumio deploy --system)

Config schema​

The config_schema field in lumio.config.json defines user-facing settings. The SchemaEditor component auto-generates a settings panel from this schema. CONFIG_FIELD_TYPES in packages/extension-types/src/config.ts defines the 18 supported field types:

TypeDescription
stringSingle-line text input
numberNumeric input
booleanToggle switch
colorColor picker with hex input
selectDropdown with predefined options
textareaMulti-line text input
sliderRange slider with min/max/step
fontFont family picker (Google Fonts catalog)
iconIcon picker (Lucide, emoji, emotes, uploads)
imageImage upload or URL
soundSound file upload or URL
multiselectMultiple selection from options
groupCollapsible group of nested fields
dividerVisual separator
infoRead-only informational text
channelMulti-platform channel picker
designerFullpage designer surface field
presetsSelectable preset bundles that write several other fields at once

Field properties​

PropertyApplies toDescription
labelAllDisplay label
defaultAll value typesDefault value
requiredAll value typesValidation requirement
min / max / stepslider, numberNumeric range constraints
placeholderstring, textareaInput placeholder text
rowstextareaNumber of visible rows
unitsliderUnit suffix (e.g. "px", "ms")
collapsedgroupStart collapsed
visible_whenAllConditional visibility based on other field values
fieldsgroupNested child field definitions
contentinfoInformational text content
optionsselect, multiselectAvailable options array
multiplemultiselectAllow multiple selections
presetsdesigner, presetsPredefined preset configurations

A separate designer_schema array in lumio.config.json defines fields for the fullpage designer surface.

Server-side validation​

Config values are validated against the config_schema before writing to extension_installs.config. Validates type match, required fields, select options, color hex format, slider range bounds, and rejects unknown keys. Both GraphQL and REST enforce validation.

Widget editor​

An extension widget instance is bound to a live installation of that extension in the active account. The instance takes its extension identity from the installation; an installation from another account or one already uninstalled cannot be used. Each widget has its own access grants: no grant, viewer, and editor allow it in the widget list; none hides it. Copying requires Editor access to the source and widgets:create. widgets:access-read lets an account member see every widget but does not grant copy access; widgets:access-grant bypasses the per-widget copy check.

Two rendering paths produce the same @lumio/ui components:

  1. config_schema -- The host-side SchemaEditor component auto-generates form fields directly from the schema definition
  2. editor.tsx -- SDK Editor Components (EditorInput, EditorTextarea, EditorSelect, EditorCheckbox, EditorSlider, EditorColorPicker, EditorGroup, EditorLabel, EditorDivider, EditorInfo, EditorFontRow, EditorButton) serialize via the Worker Reconciler and render as @lumio/ui on the host

Extensions can use config_schema only, editor.tsx only, or both (schema fields render first, custom editor below).

Advanced settings​

The AdvancedSettings panel provides:

  • Layout: Width, height, rotation, optional X/Y positioning
  • Appearance: Opacity
  • Corner Radius: Linked (all corners) or individual per-corner control
  • Effects: Text shadow, layer blur, background blur
  • Strokes: Border styling
  • Custom CSS: Fullpage Monaco Editor modal

Extension designer​

The ExtensionDesigner is a fullpage overlay with a split pane (preview + settings). It supports both designer_schema (host-side form from lumio.config.json) and designer.tsx (Worker pipeline via SDK Editor Components).

Font proxy​

DSGVO-compliant font serving through Lumio infrastructure. No browser requests go to Google.

EndpointDescription
GET /v1/fonts/\{family\}/cssCSS stylesheet with @font-face rules pointing to self-hosted woff2 URLs
GET /v1/fonts/\{family\}/\{weight\}.woff2Proxied font file, cached via lo-storage

Both endpoints are public scope (no auth required) with immutable cache headers.

Store integration​

System extensions appear in the Store alongside community extensions:

  • Category filter: Top-3 category chips (All, Built-in, Widgets) plus a "More" dropdown for remaining categories. Sort by Popular, Recent, or Top Rated.
  • Built-in filter: A dedicated "Built-in" category shows all system extensions across categories, sorted before community extensions.
  • Installed badge: Each card shows a green corner check-mark when the extension is already installed on the current account.
  • Detail page: Shows "Built-in by Lumio" instead of a developer panel, with category badge, Lucide icon, and install/uninstall actions.
  • Pagination: Load More button fetches the next 20 results.
  • Install: Normal install flow. System extensions cannot be uninstalled.
  • Icons: An icon_key that names a Lucide icon (all system extensions) renders that icon without a request. An uploaded icon loads through the dashboard proxy /api/extensions/{slug}/icon, which serves only store-visible extensions (icon-data below). Drafts and private extensions therefore show their letter placeholder in the store, the installed list, the developer's extension list and overview, the bot-module list and the widget picker, never a broken image.

Bundle files​

Bundle files support JavaScript, CSS, HTML, source maps, raster images, SVG, fonts, media, JSON, and Lottie. Every served file has X-Content-Type-Options: nosniff. HTML, SVG, JSON, source maps, Lottie, and other non-passive content also have Content-Security-Policy: sandbox and Content-Disposition: attachment; the dashboard bundle proxy applies the same headers. SVG can still be loaded as an image resource.

Discovery API​

EndpointDescription
GET /v1/extensions?visibility=system&category=widgetList system widget extensions
GET /v1/extensions?visibility=system&category=bot_moduleList system bot module extensions
GET /v1/extensions/{slug}Store detail for one extension
GET /v1/extensions/{slug}/reviews · POST /v1/extensions/{slug}/reviewsRead / write store reviews
GET /v1/extension-installs · POST /v1/extension-installs · DELETE /v1/extension-installs/{id}Per-account install lifecycle
GET /v1/extension-installs/{install_id}/storage · POST …/storagePer-install extension storage (useExtensionStorage())
GET /v1/extension-installs/{id}/logsPer-install handler logs of a bot-module or automation-node install: bot-modules:read (bot module) or automations:read (automation node), resolved from the install's category; every rejection is 404 Install not found. Messages are scrubbed; automation inputData is returned as shape only
GET /v1/store/extensions/{slug}/icon · .../screenshotsStore media assets
GET /v1/store/extensions/{id}/icon-dataIcon image bytes (PNG/JPEG/WebP) of a store-visible extension; every extension icon_url points here
GET /v1/platforms/metadataStatic platform info
platformMetadata (GraphQL)Same as REST, public query

GraphQL counterpart​

Every store operation is also a GraphQL field. All store fields combine the feature:extensions flag with an extension-store:* permission; extensionInstallLogs is gated by the install's category permission instead:

FieldKindPermission
storeExtensions · storeExtensionQueryfeature:extensions + extension-store:read
installedExtensionsQueryfeature:extensions + extension-store:read
extensionVersions · extensionReviews · extensionBundleUrlQueryfeature:extensions + extension-store:read
uninstallImpactQueryfeature:extensions + extension-store:uninstall
installExtensionMutationfeature:extensions + extension-store:install
uninstallExtensionMutationfeature:extensions + extension-store:uninstall
configureExtension · setExtensionAutoUpdate · pinExtensionVersionMutationfeature:extensions + extension-store:configure
extensionInstallLogsQuerybot-modules:read (bot-module install) or automations:read (automation-node install); same guard, redaction and Install not found error as REST
reviewExtensionMutationfeature:extensions + extension-store:review
extensionStorageQueryfeature:extensions + extension-store:read (or the install's extension token, or an overlay/widget token that renders the install)
setExtensionStorageMutationfeature:extensions + extension-store:configure (or the install's extension token); overlay and widget tokens never write

WebSocket​

ChannelGateFeature flag
ext-storage:{install_id}The install's extension token, extension-store:read in the account that owns the install, or an overlay/widget token that renders the install - the read rule of extensionStorage and its REST twinfeature:extensions
ext-logs:{extension_id}Extension owner (developer or team member)feature:extension_development
ext-install-logs:{install_id}bot-modules:read (bot-module install) or automations:read (automation-node install), resolved from the install's category like the GraphQL/REST twinsfeature:bot_module_extensions