Plugins
Lumio has a plugin system in the formal "trait + registry + metadata" sense, but almost everything it currently registers is built-in and compiled into the API binary. Dog-fooding is the explicit design goal: built-in widgets, bot modules, platforms, and event sources are all registered through the same traits that a third party would use, so the abstraction is exercised on every request. External / user-authored plugins are not yet supported — see Planned work below.
What a plugin is in Lumio
A plugin is any type that implements one of the plugin traits in crates/lo-plugins/src/traits.rs and is registered in the PluginRegistry at API startup. Each registered plugin contributes PluginMetadata (name, display_name, description, version, author, icon, category) that surfaces in the API (GET /v1/plugins) and drives the dashboard UI.
Plugin types
| Trait | Rust location | What it describes | Client counterpart |
|---|---|---|---|
WidgetPlugin | lo-plugins::traits::WidgetPlugin | Overlay widget (Spotify Now Playing, Chat Box, Alert Box, Event List). Provides default_config + validate_config. | WidgetPlugin in @lumio/plugins — the React component, settings panel, default config. |
BotModulePlugin | lo-plugins::traits::BotModulePlugin | Chat moderation module. Wraps an lo_bot_modules::BotModule implementation. | (none — runs server-side only) |
PlatformPlugin | lo-plugins::traits::PlatformPlugin | A streaming platform. Exposes AuthFlow (OAuth / ApiKey / AccessToken) and SetupStep[] for the connection wizard. | (used via the connections UI) |
EventPlugin | lo-plugins::traits::EventPlugin | An event source. Declares the platform:action event types it can produce. | (consumed by the automation builder) |
AutomationNodePlugin | lo-plugins::traits::AutomationNodePlugin | A custom node in the visual automation builder. Exposes JSON-Schema input_schema and output_schema. | AutomationNodePlugin in @lumio/plugins — the config React component. |
Registry and discovery
Server side. PluginRegistry (crates/lo-plugins/src/registry.rs) holds one HashMap<String, Box<dyn …>> per plugin type, enforces unique names within a type, and is built at startup by lo_plugins::create_default_registry (crates/lo-plugins/src/builtins/mod.rs). That function registers every built-in:
- Widgets:
SpotifyNowPlayingWidget,ChatBoxWidget,AlertBoxWidget,EventListWidget. - Bot modules:
LinkProtectionPlugin,SpamProtectionPlugin,WordFilterPlugin,TimedMessagesPlugin. - Platforms:
TwitchPlatform,YouTubePlatform,KickPlatform,TrovoPlatform,DiscordPlatform. - Events:
TwitchEvents,YouTubeEvents,KickEvents,TrovoEvents. - Automation nodes: none registered by default yet.
The resulting Arc<PluginRegistry> is injected into Actix Web state and exposed through:
GET /v1/plugins→PluginListResponse \{ widgets, bot_modules, platforms, events, automation_nodes, total \}.GET /v1/plugins/\{plugin_type\}→PluginTypeResponsefor a single category (widgets,bot_modules,platforms,events,automation_nodes).
Both endpoints require a bearer token but no special permission — they return only metadata, not configuration.
Client side. @lumio/plugins (shared/plugins/) mirrors the Rust types in TypeScript:
PluginRegistry/createPluginRegistry()inshared/plugins/src/registry.ts— managesWidgetPluginandAutomationNodePlugininstances that carry React components (component,settingsComponent,configComponent). This is what the dashboard / overlay renderer consults when it needs to mount a widget by name.PluginListResponse/PluginTypeResponsetypes match the API payloads 1:1 (snake_case on the wire, per the project convention).
Today the client registry is constructed in-app (widgets are imported and registered at build time in the web and overlay apps) — there is no runtime download of third-party React bundles.
Permissions a plugin can request
Plugins do not have an independent permission model. They run inside the API's existing RBAC: a bot-module plugin executes under the chat pipeline's account context, a platform plugin's AuthFlow declares OAuth scopes that end up in channel_connections.scopes, and widget plugins render under the overlay page's popout token or JWT. Permission strings follow the standard resource:action format enforced in RBAC & Permissions.
Authoring a plugin (today)
Because Lumio is closed-source and has no dynamic-load path, "authoring a plugin" means adding a new built-in:
- Pick the trait. Decide whether you are adding a widget, bot module, platform, event source, or automation node.
- Implement the trait in
crates/lo-plugins/src/builtins/<category>.rs. Providemetadata()(include a stablename, a semverversion, and an icon identifier), plus the category-specific methods (e.g.default_config/validate_configfor widgets,bot_module()for bot modules). - Register it in
lo_plugins::builtins::create_default_registryso it ships with every API build. - Mirror the client side (widgets / automation nodes only): add a
WidgetPluginorAutomationNodePluginimplementation in the appropriateapps/webor overlay tree, register it withcreatePluginRegistry(), and make sure themetadata.namematches the Rust side exactly. - Add translations. Any user-facing string (
display_name,description, setup-step text) must live inapps/web/messages/\{en,de\}.jsonand be referenced vianext-intl, not hardcoded. - Bump the version in the relevant
Cargo.toml/package.jsonper the project versioning rule.
The same GraphQL ↔ REST parity requirements apply: if your plugin introduces new endpoints or mutations, both protocols ship together.
Local development workflow
just stack-up— starts Postgres / TimescaleDB / Redis.just dev-api— rebuilds the API on change;create_default_registryre-runs at each restart and you'll seeRegistered widget plugin <name>/ etc. in theinfologs, plus a finaltotal = Nsummary.just dev-web— the web app picks up newly-imported client widgets on HMR.- Hit
GET /v1/pluginsto confirm your new plugin is registered. just lint-all+just test-all— clippy treats warnings as errors, and unit tests around registration live incrates/lo-plugins/src/registry.rsand per-builtin files.
Deployment
Plugins ride along with the main API binary (lumio-api) and the Next.js web / overlay bundles. There is no separate plugin artifact, no plugin-upload endpoint, and no runtime install. To ship a new plugin, release a new version of the monorepo.
Planned work
The following are explicitly not implemented yet. Do not rely on them:
- Third-party / user-authored plugins.
PluginRegistry::register_*is called only at startup bycreate_default_registry. There is no API, no signed-bundle format, and no sandbox for external code. - Dynamic / hot-reloadable plugins. Every change requires a rebuild + redeploy.
- Permission-scoped plugin contexts. A built-in plugin runs with full API privileges; there is no per-plugin capability token yet.
- Marketplace / plugin registry service. No discovery, pricing, or review flow exists.
Everything on that list is intentional for now — the trait surface is stable enough for internal dog-fooding, but externalising it needs a sandbox story first. Track follow-up work in the roadmap before designing against it.
Key files
crates/lo-plugins/src/lib.rs— crate entry point, re-exports.crates/lo-plugins/src/traits.rs— the five plugin traits.crates/lo-plugins/src/metadata.rs—PluginMetadata,PluginCategory.crates/lo-plugins/src/registry.rs—PluginRegistryimplementation.crates/lo-plugins/src/builtins/mod.rs—create_default_registrybootstrap.apps/api/src/routes/plugins.rs—GET /v1/plugins+GET /v1/plugins/\{plugin_type\}.shared/plugins/src/types.ts,registry.ts,index.ts— client-side@lumio/pluginsmirror.