Skip to main content

Plugin Manifest Guide

The manifest.json file is the heart of your Lumia Stream plugin. It defines metadata, configuration options, and capabilities.

Basic Structure

{
"id": "unique_plugin_id",
"name": "Human Readable Name",
"version": "1.0.0",
"author": "Your Name",
"description": "Brief description",
"lumiaVersion": "^9.0.0",
"category": "utilities",
"config": {
"settings": [],
"actions": [],
"variables": [],
"alerts": [],
"translations": {}
}
}

Required Fields

Basic Information

  • id (string): Unique identifier for your plugin. Use letters, numbers, or underscores, no spaces or hyphens.
  • name (string): Human-readable plugin name.
  • version (string): Semantic version (e.g., "1.0.0").
  • author (string): Plugin author name.
  • description (string): Brief description of what the plugin does.
  • lumiaVersion (string): Compatible Lumia Stream version (semver range).
  • category (string): Plugin category (see categories below).

Optional Fields

Extended Information

  • email (string): Author's email address.
  • website (string): Plugin or author website URL.
  • repository (string): Source code repository URL.
  • license (string): License type (e.g., "MIT", "GPL-3.0").
  • keywords (string): Comma separated list of keywords for search.
  • icon (string): Plugin icon filename (PNG recommended).
  • changelog (string): Markdown changelog content or a relative path to a .md file in the plugin package.
  • bundle (object): Optional install-time bundle for commands and overlays.

Bundle Content (Optional)

Use bundle when you want a plugin to install ready-made commands or overlays along with the plugin itself.

{
"id": "my_plugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "You",
"description": "Example",
"lumiaVersion": "^9.0.0",
"category": "utilities",
"config": { "settings": [] },
"bundle": {
"commands": [
"bundle/commands/hype_command.lumia",
"bundle/commands/wow_command.lumia"
],
"overlays": ["overlay-marketplace-upload-id", "another-overlay-upload-id"]
}
}

bundle.commands must be an array of exported command file paths (.lumia / .lumiastream).

bundle.overlays must be an array of marketplace upload IDs or shared download URLs. Example URL format: https://api.lumiastream.com/shared/download/<overlay-id>.

Each ID must reference a public + approved marketplace overlay.

Plugin Categories

The Lumia marketplace recognises the following categories (strings are case-sensitive):

  • system: Core Lumia features and internal tooling
  • platforms: Streaming platform integrations (Twitch, YouTube, Kick, etc.)
  • apps: Third-party app integrations
  • lights: General lighting control providers
  • switch: Smart switch and relay integrations
  • deck: Control deck hardware and software
  • protocols: Network or automation protocols (OSC, MIDI, etc.)
  • keylight: Key light devices
  • devices: Miscellaneous hardware integrations
  • utilities: General purpose utilities and helpers
  • overlays: Overlay and visual experiences
  • audio: Audio processing and sound integrations
  • chat: Chat interaction tools
  • development: Development, testing, and debugging utilities

Lights configuration (for lights category)

If your plugin provides lights, add a config.lights block so the PluginAuth UI can render discovery/manual-add controls and a selection list. Lights are saved by the Lumia UI; plugins should not mutate light state directly.

{
"id": "my_lights_plugin",
"name": "My Lights",
"category": "lights",
"config": {
"settings": [],
"lights": {
"search": {
"buttonLabel": "Discover lights",
"helperText": "Runs your searchLights hook"
},
"manualAdd": {
"buttonLabel": "Add light",
"helperText": "Enter details for a device",
"fields": [
{ "key": "name", "label": "Name", "type": "text", "required": true },
{ "key": "id", "label": "ID (optional)", "type": "text" },
{ "key": "ip", "label": "IP (optional)", "type": "text" }
]
},
"displayFields": [
{ "key": "name", "label": "Name" },
{ "key": "ip", "label": "IP", "fallback": "No IP" }
],
"emptyStateText": "No lights yet. Discover or add one."
}
}
}

Runtime hooks for lights plugins:

  • Implement searchLights to return an array of discovered lights for the UI to save.
  • Implement addLight to handle manual-add requests and return the updated array.
  • Implement onLightChange to receive color/brightness/power updates for your lights.
  • Implement searchThemes to return Studio theme options (for example effects/modes).

Note: Selection and persistence are handled by the PluginAuth UI; plugins should not call registerLights/updateChosenLights.

Plugs configuration (optional for plug/accessory integrations)

If your plugin provides plug/accessory control, add a config.plugs block so the PluginAuth UI can render discovery/manual-add controls and a selection list.

{
"id": "my_plug_plugin",
"name": "My Plugs",
"category": "devices",
"config": {
"settings": [],
"plugs": {
"search": {
"buttonLabel": "Discover plugs",
"helperText": "Runs your searchPlugs hook"
},
"manualAdd": {
"buttonLabel": "Add plug",
"helperText": "Enter details for a plug",
"fields": [
{
"key": "name",
"label": "Alias Name",
"type": "text",
"required": true
},
{
"key": "mac",
"label": "MAC Address",
"type": "text",
"required": true
},
{ "key": "model", "label": "Model (optional)", "type": "text" }
]
},
"displayFields": [
{ "key": "name", "label": "Name" },
{ "key": "model", "label": "Model", "fallback": "Unknown model" }
],
"emptyStateText": "No plugs yet. Discover or add one."
}
}
}

Runtime hooks for plugs/accessories:

  • Implement searchPlugs to return an array of discovered plugs for the UI to save.
  • Implement addPlug to handle manual-add requests and return the updated array.
  • Implement onPlugChange to receive runtime state updates (state: true/false) for selected plugs.

Note: Selection and persistence are handled by the PluginAuth UI; plugins should not call registerAccessories/updateChosenAccessories.

Studio Theme Configuration (optional for lights plugins)

Use config.themeConfig when your lights plugin supports Studio theme scenes/effects/presets.

{
"config": {
"themeConfig": {
"keyForThemes": "effects",
"hasEffects": true,
"sceneType": "light-theme",
"displayKey": "name",
"showIndividualLights": true
}
}
}

Theme runtime contract:

  • searchThemes should return either:
    • an array (stored under themeConfig.keyForThemes, default scenes)
    • or an object containing one or more of scenes, effects, presets
  • when a Studio theme is executed, selected theme values are provided to onLightChange in config.rawConfig.theme

Configuration

The config object defines your plugin's interactive elements:

AI Provider Support

If your plugin should appear as an AI provider (like ChatGPT/DeepSeek) in Lumia features that support AI routing, declare:

{
"config": {
"hasAI": true
}
}

When hasAI is enabled, implement:

  • aiPrompt(config) to process prompt requests and return text.
  • aiModels(config?) to return available model options for dropdowns.

Settings

Settings create a configuration UI for users:

{
"config": {
"settings": [
{
"key": "apiKey",
"label": "API Key",
"type": "password",
"placeholder": "Enter your API key",
"helperText": "Get this from your service dashboard",
"required": true
},
{
"key": "pollInterval",
"label": "Poll Interval (seconds)",
"type": "number",
"defaultValue": 30,
"validation": {
"min": 5,
"max": 300
}
},
{
"key": "notifications",
"label": "Enable Notifications",
"type": "toggle",
"defaultValue": true
}
]
}
}

Organizing Settings with Sections and Groups

Use these layout properties to keep plugin settings easy to navigate:

  • section: creates top-level settings tabs
  • sectionOrder: controls tab order (ascending)
  • group: creates a grouped container inside a section for related fields

Quick guidance:

  • Use only section when you just need tabs.
  • Add group when a tab has multiple clusters (for example, one cluster per game).

UI rendering shape:

Plugin Settings
├─ Tab: Connection (section="Connection")
│ ├─ Field: API Key
│ └─ Field: Poll Interval
└─ Tab: Detection (section="Detection")
├─ Group: Valorant Rules (group="valorant_rules")
│ ├─ Field: Kill Template
│ └─ Field: ROI
└─ Group: Rocket Rules (group="rocket_rules")
├─ Field: Goal Template
└─ Field: ROI

Example:

{
"config": {
"settings": [
{
"key": "enabledGames",
"label": "Enabled Games",
"type": "select",
"multiple": true,
"section": "Detection",
"sectionOrder": 2,
"options": [
{ "label": "Valorant", "value": "valorant" },
{ "label": "Overwatch", "value": "overwatch" }
]
},
{
"key": "valorantTemplate",
"label": "Valorant Kill Template",
"type": "file",
"section": "Detection",
"group": {
"key": "valorant_rules",
"label": "Valorant Rules",
"visibleIf": {
"key": "enabledGames",
"equals": "valorant"
}
}
},
{
"key": "valorantRoi",
"label": "Valorant ROI",
"type": "roi",
"section": "Detection",
"group": "valorant_rules"
}
]
}
}

For complete behavior details (including visibleIf, fallback rules, and shorthand/object group forms), see docs/field-types-reference.md.

Avoid adding settings that exist only for testing or debugging. Keep settings user-facing and essential.

If you provide settings_tutorial (markdown or a relative .md file path), it renders as a setup guide in the auth screen.

To provide a tutorial that is specific to actions, use actions_tutorial (markdown or a relative .md file path). When present, it renders in the Actions editor.

Populate both tutorials with clear, step-by-step instructions that a first-time user can follow without guesswork.

Tutorials can include local images bundled with the plugin. Reference them with a relative path and they will resolve from the plugin root at runtime:

### Setup

![API Key](clickup_tutorial_api_key.jpg)
![List ID](clickup_tutorial_list_id.jpg)

Tutorials can also embed playable media directly in markdown-supported HTML tags.

Tutorial Media Embeds

Use raw HTML tags inside settings_tutorial or actions_tutorial for embedded media:

### Video Walkthrough

<iframe
width="100%"
height="315"
src="https://www.youtube.com/watch?v=VCd0kYWLvMQ"
title="Setup walkthrough"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allowfullscreen
></iframe>
### Local Video

<video controls preload="metadata">
<source src="./setup-demo.mp4" type="video/mp4" />
</video>
### Audio Instructions

<audio controls preload="metadata">
<source src="./voiceover.mp3" type="audio/mpeg" />
</audio>

Media behavior and safety rules:

  • iframe embeds are restricted to YouTube (youtube.com, m.youtube.com, youtu.be).
  • YouTube watch/share URLs are normalized to embed URLs automatically.
  • video, audio, and source src values allow http, https, file, blob, and data:audio/* / data:video/*.
  • Relative media paths are resolved from the plugin root, so include those files in your plugin package.
  • Keep a plain fallback link near embeds so users can still open media externally if playback is blocked on their system.

Plugin Translations

Plugins can provide translation bundles under config.translations. These bundles are loaded into i18next under the plugin namespace (the plugin id) when the plugin loads.

config.translations supports two shapes:

  • a language-map object ({ en: {...}, es: {...} })
  • a relative .json file path that contains that language-map object
{
"id": "clickup",
"name": "ClickUp",
"version": "1.0.0",
"author": "Lumia Stream",
"description": "ClickUp integration",
"lumiaVersion": "^9.0.0",
"category": "apps",
"config": {
"translations": "./translations.json"
}
}

Example translations.json:

{
"en": {
"task_due_date": "Task Due Date",
"clickup_task_due_date": "Task Due Date"
},
"de": {
"task_due_date": "Fälligkeitsdatum",
"clickup_task_due_date": "Fälligkeitsdatum"
}
}

You can also inline the language map directly in manifest.json:

{
"config": {
"translations": {
"en": {
"task_due_date": "Task Due Date"
}
}
}
}

Use keys from supported Lumia languages (en, es, fr, ar, nl, sv, de, tr, zh, pt). Regional tags (for example en-US) fall back to their base language when available.

Multi-Value Settings

Use select with multiple: true when you need users to select or enter multiple values.

{
"config": {
"settings": [
{
"key": "lightIds",
"label": "Light IDs",
"type": "select",
"multiple": true,
"allowTyping": true,
"helperText": "Add one or more light identifiers or choose from suggestions",
"options": [
{ "label": "Keylight 1", "value": "keylight-1" },
{ "label": "Keylight 2", "value": "keylight-2" }
],
"defaultValue": ["keylight-1", "keylight-2"]
}
]
}
}

OAuth (Lumia-managed)

Plugins can request OAuth via Lumia's auth service so users can authorize in-app and store tokens in plugin settings. To enable this, add an oauth block under config.

UI rendering shape:

Plugin Auth Screen
├─ OAuth Card
│ ├─ Title: OAuth
│ ├─ Helper Text (oauth.helperText)
│ ├─ Button: Authorize (oauth.buttonLabel)
│ ├─ Button: Copy auth link
│ └─ Status / Error Text
└─ Settings Form
└─ Regular plugin settings fields (config.settings)

OAuth flow:

  1. User clicks Authorize.
  2. Lumia opens the OAuth URL (in-app or browser based on openInBrowser).
  3. Provider redirects back to Lumia.
  4. Lumia maps returned tokens to plugin settings via tokenKeys.
  5. Tokens are available in runtime as this.settings.<tokenKey>.
{
"config": {
"oauth": {
"buttonLabel": "Authorize Fitbit",
"helperText": "Connect your Fitbit account to pull health metrics.",
"openInBrowser": false,
"scopes": ["activity", "heartrate"],
"extraParams": "external=true",
"tokenKeys": {
"accessToken": "accessToken",
"refreshToken": "refreshToken",
"tokenSecret": "tokenSecret"
}
}
}
}

Notes:

  • The OAuth provider is always the plugin ID within Lumia. If you need a new OAuth 2.0 integration, contact Lumia Stream on Discord or email dev@lumiastream.com so we can enable it for your plugin.
  • serviceUrl can be provided to override the default auth URL entirely.
  • scopes are provider-specific. When set, they are sent to Lumia's OAuth service as a comma-separated list.
  • Tokens are stored into your plugin settings using the tokenKeys mapping and are available via this.settings in your plugin.

Custom Auth Display (PluginAuth Embedded UI)

If your plugin needs a custom setup flow (for example, patching fixtures or testing hardware output), you can render your own HTML/CSS/JS inside PluginAuth.

Manifest shape:

{
"config": {
"custom_auth_display": {
"entry": "./auth/index.html",
"autoAutoOpen": true,
"authButtonLabel": "Open Device Setup",
"title": "Device Setup"
}
}
}

Field behavior:

  • entry (required): relative path to your HTML file from plugin root.
  • title (required): modal title shown in PluginAuth.
  • autoAutoOpen (optional boolean): auto-opens the modal when PluginAuth loads.
  • authButtonLabel (optional string): label for the manual open button in PluginAuth.

Runtime hooks:

  • onCustomAuthDisplaySignal(config) receives generic signals from your embedded page.
  • onCustomAuthDisplayClose(config) runs when the modal closes.

In your embedded page, Lumia injects window.customAuthDisplay:

  • window.customAuthDisplay.signal(type, payload) sends a generic signal to onCustomAuthDisplaySignal.
  • window.customAuthDisplay.close(reason?) closes the modal and triggers onCustomAuthDisplayClose.
  • window.customAuthDisplay.signal('close') is also supported as a close mode.

To keep custom auth pages visually consistent with Lumia Stream, use Lumia's base theme colors and component conventions.

Suggested palette:

:root {
--background: #191743;
--containerbackground: #1f1f3a;
--cardbackground: #15142b;
--cardborder: #393853;
--primary: #ff4076;
--secondary: #535395;
--white: #ffffff;
--white2: #cac9d5;
--success: #5dda6c;
--warning: #dcc984;
--error: #fd5454;
--transwhite: rgba(255, 255, 255, 0.05);
}

Suggested component rules:

  • Font: Roboto, sans-serif.
  • Cards/panels: radius 16px, border 1px solid #393853, dark panel fill (#1f1f3a/#15142b).
  • Inputs/selects/textareas:
    • border 1px solid #393853
    • background rgba(255,255,255,0.05) or #1f1f3a
    • text #ffffff, secondary/help text #cac9d5
    • focus border #cac9d5 (or #ff4076 for primary emphasis)
  • Buttons:
    • primary actions use #ff4076 tint + stronger accent border
    • secondary actions use neutral dark fill + subtle border/hover
    • status actions use #5dda6c, #dcc984, #fd5454 tinted variants
  • Spacing: use 8px scale (8/12/16/24) for consistent density.

For AI-assisted UI generation, include these tokens and rules in your prompt so generated custom auth pages match Lumia out of the box.

Native chatbot support

If your plugin can post chat messages on its own platform, declare chatbot support in the manifest:

{
"config": {
"hasChatbot": true
}
}

config.hasChatbot is a boolean flag. Set it to true to enable plugin-native chatbot routing.

Runtime routing behavior:

  1. Lumia calls chatbot(config) on your plugin when implemented.
  2. Lumia does not fall back to actions() for chatbot routing.

Native moderation command support

If your plugin supports chat moderation actions in Dashboard Chatbox / API, declare supported commands:

{
"config": {
"modcommandOptions": ["delete", "ban", "timeout"]
}
}

Allowed values are:

  • delete
  • copy
  • translate
  • shoutout
  • ban
  • unban
  • timeout
  • add-vip
  • remove-vip
  • add-moderator
  • remove-moderator

When declared, Lumia routes these commands to modCommand(type, value) in your plugin runtime.

Heart rate source support

If your plugin reads a live heart rate (BPM), from a wearable, a Bluetooth heart-rate monitor, or a third-party API, declare it as a heart-rate source:

{
"config": {
"hasHeartrate": true
}
}

config.hasHeartrate is a boolean flag. When true, Lumia treats your plugin like the built-in HypeRate/Pulsoid integrations and shows the heart-rate zone Alerts UI (Pulse levels) for it instead of plain alerts.

The flag is only the UI half. To feed the shared heart-rate system, call updateHeartRate(bpm) from your runtime whenever a new reading arrives:

await this.lumia.updateHeartRate(bpm);

That single call drives the {{heart_rate}} overlay variable, the Pulse heart-rate zone alerts, calorie tracking, and Studio heart-rate light reactions, the same pipeline the native heart-rate integrations use. See updateHeartRate in the API Reference.

Settings support the same field types as action fields, plus three settings-only structured types: named_map (labeled name-to-value rows), json (raw JSON editor), and roi (region-of-interest coordinates). Settings also support disabled: true to render a field read-only in PluginAuth.

Use named_map instead of a freeform textarea when users should define multiple named entries (for example sound name -> file, feed name -> URL).

For all field types, properties, validation options, and examples see Field Types Reference.

Actions

Actions allow users to trigger plugin functionality manually:

{
"config": {
"actions": [
{
"type": "send_message",
"label": "Send Custom Message",
"description": "Send a custom message to the service",
"fields": [
{
"key": "message",
"label": "Message",
"type": "textarea",
"required": true
},
{
"key": "channel",
"label": "Channel",
"type": "select",
"options": [
{ "label": "General", "value": "general" },
{ "label": "Alerts", "value": "alerts" }
]
}
]
},
{
"type": "update_status",
"label": "Update Status",
"description": "Update the current status message",
"fields": [
{
"key": "status",
"label": "Status",
"type": "text",
"required": true
}
]
}
]
}
}

Avoid test-only actions (for example, test connection or refetch). Actions should map to real user workflows.

To auto-refresh dynamic options when a user selects the action type, set refreshOnChange on the action.

{
"type": "send_message",
"label": "Send Custom Message",
"refreshOnChange": true,
"fields": []
}

acceptedVariables on actions is optional, but when used each key must be prefixed with your plugin id (for example, myplugin_song_name). Unprefixed keys are ignored by Lumia.

Use action acceptedVariables for action-specific result data. Do not mirror one-off action payloads into global config.variables unless that value must persist outside the action result.

Variable Functions

Variable functions let plugins return values inline in templates (similar to {{ai_prompt=...}}).

Define them under config.variableFunctions:

{
"config": {
"variableFunctions": [
{
"key": "my_function",
"label": "My Function",
"description": "Use {{my_function=value}} to return a value."
}
]
}
}

Usage example:

{{my_function=message|thread|model}}
  • The text after = is passed to the plugin as a raw string (value).
  • A convenience args array is provided by splitting the raw string on |.
  • Your plugin handles the logic in variableFunction() and returns a string.

Guidelines:

  • Keep variable functions fast. They run during template resolution.
  • Do not log excessively or retry aggressively.
  • Return a plain string or an object with { value, variables }.

Logging Guidance

Avoid excessive logging. High-frequency logs can quickly fill the user's Logs dashboard. Prefer concise logs and only emit details for errors or explicit user actions. Avoid per-plugin log wrappers unless they add real value.

Action fields support: text, datetime, email, url, number, textarea, color, select, checkbox, toggle, slider, file, media. Note: password, named_map, json, and roi are not available in action fields.

Key action-field behaviors:

  • Set allowVariables: true to enable {{variable}} insertion on a field. When omitted, variables are disabled, including on select fields with allowTyping.
  • Set allowTyping: true on select to allow custom typed values alongside dropdown options.
  • Set multiple: true on select to allow multi-value selection (value becomes an array).
  • allowTyping and multiple can be combined on the same select field.

For full property lists and examples see Field Types Reference.

Variables

Variables define data that your plugin provides to Lumia Stream:

Variable names in config.variables can be unprefixed. Lumia namespaces them internally.

When you reference variables in action acceptedVariables or return them from actions() via newlyPassedVariables, keys must be fully prefixed with your plugin id (<pluginId>_key). Unprefixed keys are ignored.

Global variable display text is resolved from your plugin translations (config.translations) using your plugin namespace.

Best practice: keep config.variables minimal and durable. Reserve globals for long-lived state (for example totals, latest known status, profile metadata). For action outputs and request-specific values, use action acceptedVariables / newlyPassedVariables and alert extraSettings payloads instead of creating many global variables.

{
"config": {
"variables": [
{
"name": "follower_count",
"system": true,
"origin": "twitch",
"value": 0,
"example": "follower_count"
},
{
"name": "last_follower",
"system": true,
"origin": "twitch",
"value": "",
"example": "last_follower"
}
]
}
}

Alerts

Alerts define events that your plugin can trigger:

{
"config": {
"alerts": [
{
"title": "New Follower",
"key": "follow",
"acceptedVariables": ["follower_count", "last_follower"],
"defaultMessage": "{{last_follower}} just followed! Welcome!",
"variationConditions": []
},
{
"title": "Stream Started",
"key": "streamStart",
"acceptedVariables": ["stream_title", "game"],
"defaultMessage": "Stream is now live! Playing {{game}}"
}
]
}
}

Optional alert defaults can be provided under defaults to control how Lumia initializes the base alert state. For example, disableBaseAlert can enforce variations-only behavior.

{
"title": "High CPU Usage",
"key": "cpu_high",
"defaultMessage": "",
"defaults": {
"disableBaseAlert": true
}
}

Alert Variations (variationConditions)

Use variationConditions when an alert can fire with multiple sub-types (for example, different tiers of a subscription or thresholds of a donation) and you want creators to configure each variation independently.

  • type: One of the condition identifiers exposed by LumiaVariationConditions (see lumia-types/src/alert.types.ts:6). Examples: EQUAL_SELECTION, GIFT_SUB_EQUAL, GREATER_NUMBER, RANDOM.
  • description (optional): Helper text shown in the Lumia UI.
  • dynamicOptions (optional, EQUAL_SELECTION only): When true, Lumia shows dropdown suggestions but also allows typed custom values.
  • selections (optional): Only used with EQUAL_SELECTION; supplies the dropdown values the creator can pick from.
    • label: How the option appears in the Lumia UI.
    • value: The literal tier/value you expect to receive at runtime (compared against dynamic.value).
    • message (optional): Override for defaultMessage when this variation is active.

Example manifest entry with variations:

{
"title": "Gifted Membership",
"key": "giftedMembership",
"defaultMessage": "{{gifter}} gifted a membership!",
"acceptedVariables": ["gifter", "recipient", "gift_count"],
"variationConditions": [
{
"type": "EQUAL_SELECTION",
"description": "Tier level (compares against dynamic.value).",
"dynamicOptions": true,
"selections": [
{
"label": "Single gift",
"value": "Tier1"
},
{
"label": "Premium tier",
"value": "Tier2",
"message": "{{gifter}} just unlocked the premium tier!"
}
]
},
{
"type": "GIFT_SUB_EQUAL",
"description": "Exact gift bundle size (compares against dynamic.giftAmount)."
},
{
"type": "GIFT_SUB_GREATER",
"description": "Gift bundle size or larger (compares against dynamic.giftAmount)."
}
]
}

At runtime, trigger the alert with variation data in dynamic and alert variables in extraSettings:

await this.lumia.triggerAlert({
alert: "giftedMembership",
dynamic: {
value: "Tier2", // value compared by EQUAL_SELECTION
},
extraSettings: {
gifter: "StreamerFan42",
recipient: "LuckyViewer",
gift_count: 5,
},
});

Important:

  • dynamic is only for variationConditions.
  • Use dynamic.value for standard comparisons.
  • For specialized comparisons, pass direct fields like giftAmount, subMonths, currency, or isGift.
  • Plugin-triggered alerts do not accept dynamic.name; plugin runtime strips it automatically.
  • extraSettings can contain any key/value pairs and is the payload used for alert variables/templates.

If you need multiple variation comparisons, trigger separate alerts with the appropriate dynamic payload for each event.

Variation Generation

Use variation generation when Lumia should build many alert variations from plugin-managed data instead of having the creator add them one by one.

  • variationGeneration (optional): Configures the modal shown before generation runs.
    • title (optional): Modal title.
    • description (optional): Helper text shown above the form.
    • buttonLabel (optional): Label used for the alert-page button.
    • generateLabel (optional): Label used for the modal submit button.
    • fields (optional): Uses the same PluginFormField[] schema as other plugin forms, so you can declare checkboxes, inputs, selects, sliders, and other supported field types.

Example manifest entry:

{
"title": "Gift Event",
"key": "gift",
"defaultMessage": "{{username}} sent {{giftName}}",
"variationGeneration": {
"title": "Generate Gift Variations",
"description": "Create one variation for each supported gift.",
"buttonLabel": "Generate Gifts",
"generateLabel": "Generate",
"fields": [
{
"key": "minimumCost",
"label": "Minimum cost",
"type": "number",
"defaultValue": 1,
"min": 0
},
{
"key": "includeImages",
"label": "Include thumbnails",
"type": "checkbox",
"defaultValue": true
}
]
}
}

When the creator clicks the button, Lumia calls your runtime generateVariations() method:

async generateVariations(config) {
const { alertKey, options, existingVariations } = config;
if (alertKey !== 'gift') return [];

return [
{
name: 'Rose',
conditionType: 'EQUAL_SELECTION',
condition: 'Rose',
image: 'https://example.com/rose.png',
},
];
}

Guidelines:

  • Return only new variations; skip duplicates against existingVariations.
  • condition / conditionType should match the alert's variationConditions.
  • image is optional and becomes the variation thumbnail.
  • Lumia still does its own duplicate filtering before saving, but plugins should avoid returning duplicates themselves.

Variation Payload Contract (Plugins)

For plugin-triggered alerts, Lumia applies these runtime rules:

  • Variation matching uses dynamic (not extraSettings).
  • Plugin alert keys are auto-prefixed as <pluginId>-<alert> unless already prefixed.
  • Reserved identity keys are stripped from plugin payloads: pluginId, platform, site, origin.
  • dynamic.name from plugins is stripped and ignored.
  • dynamic is merged into extraSettings for downstream consumers; if the same key exists in both objects, dynamic wins.

Use these dynamic fields for each variation type:

Variation Condition TypeDynamic Fields To Send
EQUAL_NUMBER, EQUAL_SELECTION, EQUAL_STRING, GREATER_NUMBER, LESS_NUMBERvalue
EQUAL_CURRENCY_NUMBER, GREATER_CURRENCY_NUMBERvalue, currency
GIFT_SUB_EQUAL, GIFT_SUB_GREATERgiftAmount
IS_GIFTisGift and/or giftAmount
SUBSCRIBED_MONTHS_EQUAL, SUBSCRIBED_MONTHS_GREATERsubMonths
EQUAL_USERNAMEusername (or value)
EQUAL_USER_LEVELlumiauserlevels
COUNT_IS_MULTIPLE_OFtotal (previousTotal optional for deterministic threshold-crossing behavior)
RANDOMno dynamic field required

If no matching selection is found for the provided condition values (or no dynamic payload is supplied), Lumia falls back to the base alert configuration and defaultMessage.

To also show a plugin-triggered alert inside the Event List, opt in explicitly:

await this.lumia.triggerAlert({
alert: "giftedMembership",
showInEventList: true,
extraSettings: {
gifter: "StreamerFan42",
recipient: "LuckyViewer",
gift_count: 5,
},
});

Recommendation: keep showInEventList disabled by default. Most utility/app/device plugins should not write every plugin-triggered alert to Event List. Enable it for platform/event-source integrations when those events are meant to appear there.

Tip: LumiaDynamicCondition in lumia-types/src/alert.types.ts:99 documents the dynamic fields used by variation checkers (value, currency, subMonths, giftAmount, etc.).

Complete Example

Here's a complete manifest for a hypothetical Discord integration plugin:

{
"id": "discord_integration",
"name": "Discord Integration",
"version": "2.1.0",
"author": "Lumia Stream",
"email": "dev@lumiastream.com",
"website": "https://lumiastream.com/plugins/discord",
"repository": "https://github.com/LumiaStream/discord-plugin",
"description": "Integrate your Discord server with Lumia Stream",
"license": "MIT",
"lumiaVersion": "^9.0.0",
"keywords": "discord, chat, community, notifications",
"category": "platforms",
"icon": "discord-icon.png",
"changelog": "# Changelog\n\n## 2.1.0\n- Added voice channel activity tracking\n- Improved message filtering\n- Bug fixes for role mentions\n\n## 2.0.0\n- Complete rewrite with new Discord API\n- Added slash command support\n- Enhanced security",
"config": {
"settings": [
{
"key": "botToken",
"label": "Bot Token",
"type": "password",
"placeholder": "Your Discord bot token",
"helperText": "Create a bot application at https://discord.com/developers/applications",
"required": true
},
{
"key": "guildId",
"label": "Server ID",
"type": "text",
"placeholder": "Your Discord server ID",
"helperText": "Right-click your server name and select 'Copy Server ID'",
"required": true,
"validation": {
"pattern": "^[0-9]{17,19}$"
}
},
{
"key": "channels",
"label": "Monitored Channels",
"type": "textarea",
"placeholder": "Channel IDs, one per line",
"helperText": "Leave empty to monitor all channels",
"defaultValue": ""
},
{
"key": "messageFilter",
"label": "Message Filter",
"type": "select",
"defaultValue": "all",
"options": [
{ "label": "All Messages", "value": "all" },
{ "label": "Mentions Only", "value": "mentions" },
{ "label": "Commands Only", "value": "commands" }
]
},
{
"key": "enableVoiceTracking",
"label": "Track Voice Activity",
"type": "toggle",
"defaultValue": false,
"helperText": "Monitor voice channel joins/leaves"
}
],
"actions": [
{
"type": "send_message",
"label": "Send Message",
"description": "Send a message to a Discord channel",
"fields": [
{
"key": "channelId",
"label": "Channel ID",
"type": "text",
"required": true
},
{
"key": "message",
"label": "Message",
"type": "textarea",
"required": true,
"validation": {
"maxLength": 2000
}
}
]
},
{
"type": "update_status",
"label": "Update Bot Status",
"description": "Update the bot's activity status",
"fields": [
{
"key": "activity",
"label": "Activity Type",
"type": "select",
"options": [
{ "label": "Playing", "value": "PLAYING" },
{ "label": "Listening", "value": "LISTENING" },
{ "label": "Watching", "value": "WATCHING" },
{ "label": "Streaming", "value": "STREAMING" }
]
},
{
"key": "text",
"label": "Status Text",
"type": "text",
"placeholder": "What is the bot doing?"
}
]
}
],
"variables": [
{
"name": "member_count",
"system": true,
"origin": "discord",
"value": 0,
"example": "member_count"
},
{
"name": "online_count",
"system": true,
"origin": "discord",
"value": 0,
"example": "online_count"
},
{
"name": "voice_count",
"system": true,
"origin": "discord",
"value": 0,
"example": "voice_count"
},
{
"name": "last_message",
"system": true,
"origin": "discord",
"value": "",
"example": "last_message"
},
{
"name": "last_user",
"system": true,
"origin": "discord",
"value": "",
"example": "last_user"
}
],
"alerts": [
{
"title": "New Message",
"key": "message",
"acceptedVariables": ["last_message", "last_user"],
"defaultMessage": "{{last_user}}: {{last_message}}"
},
{
"title": "Member Joined",
"key": "memberJoin",
"acceptedVariables": ["member_count", "last_user"],
"defaultMessage": "{{last_user}} joined the Discord! ({{member_count}} total)"
},
{
"title": "Voice Channel Activity",
"key": "voiceActivity",
"acceptedVariables": ["voice_count", "last_user"],
"defaultMessage": "{{last_user}} joined voice ({{voice_count}} in voice)"
}
]
}
}

Validation

The SDK includes manifest validation. Common validation errors:

  • Invalid semver: Version must follow semantic versioning
  • Missing required fields: All required fields must be present
  • Invalid category: Category must be one of the allowed values
  • Duplicate keys: Setting/action/variable keys must be unique
  • Invalid types: Field types must be valid
  • Circular dependencies: Alert conditions cannot create loops

Best Practices

  1. Unique IDs: Use your organization/username prefix (e.g., "mycompany-plugin-name")
  2. Semantic Versioning: Follow semver for version numbers
  3. Clear Descriptions: Write descriptive labels and help text
  4. Sensible Defaults: Provide good default values
  5. Validation: Use validation rules to prevent user errors
  6. Documentation: Include comprehensive descriptions and examples
  7. Testing: Test your manifest with the validation tools
  8. Tutorials: Provide clear settings_tutorial and actions_tutorial steps
  9. No Test Controls: Avoid test-only settings and actions
  10. Retry Discipline: Implement a disconnect flow, use exponential backoff with a retry cap, mark the plugin disconnected when retries are exhausted, and pause polling until an explicit reconnect trigger
  11. Minimal Logs: Log errors and explicit user actions only

Localization

See Plugin Translations above for full config.translations documentation, including inline language maps, external .json file paths, supported language codes, and runtime key resolution.