Triggers
The triggers field in lumio.config.json defines when your bot module handlers fire. Triggers are only allowed when category is "bot_module" — other extension categories with a triggers field are rejected at validation.
Overview
{
"category": "bot_module",
"triggers": {
"commands": [...],
"keywords": [...],
"patterns": [...],
"events": [...],
"timers": [...]
}
}
Each trigger type has different matching behavior and execution characteristics:
| Trigger | Where matched | Execution | Latency |
|---|---|---|---|
| Command | Bot (prefix match) | Sync HTTP (500ms deadline) | ~50-200ms |
| Keyword | Bot (substring) | Async (fire-and-forget) | Fire-and-forget |
| Pattern | Bot (compiled regex) | Async (fire-and-forget) | Fire-and-forget |
| Event | Worker (event subscription) | Async | Fire-and-forget |
| Timer | Worker (internal clock) | Async (self-triggered) | N/A |
Commands
Chat commands triggered by a prefix (e.g., !points).
{
"triggers": {
"commands": [
{
"name": "points",
"description": "Show your current points balance",
"cooldown_global": 5,
"cooldown_user": 10,
"min_role": "everyone"
}
]
}
}
Command fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Command name (without ! prefix). Alphanumeric + underscore, 1-32 characters. |
description | string | Yes | Short description shown in the dashboard and Discord slash command registration. |
cooldown_global | integer | No | Global cooldown in seconds (0 = no cooldown). Applies to all users. |
cooldown_user | integer | No | Per-user cooldown in seconds (0 = no cooldown). Applies individually. |
min_role | string | No | Minimum role required to use the command. Default: "everyone". |
discord_options | array | No | Discord slash command options. Ignored on other platforms. See Discord Slash Commands. |
Command validation rules
| Rule | Constraint |
|---|---|
| Name format | ^[a-zA-Z0-9_]+$, 1-32 characters |
| Max commands per extension | 20 |
| Cooldown values | Integer >= 0 |
min_role values
| Value | Who can use |
|---|---|
"everyone" | All viewers |
"follower" | Followers and above |
"subscriber" | Subscribers and above |
"vip" | VIPs and above |
"moderator" | Moderators and above |
"broadcaster" | Broadcaster only |
Users can raise (but not lower) the minimum role in their dashboard override settings.
Keywords
Substring matches against chat messages. When a keyword appears anywhere in a message, the associated handler fires.
{
"triggers": {
"keywords": ["gg", "nice shot", "pog"]
}
}
Keyword validation rules
| Rule | Constraint |
|---|---|
| Minimum length | 3 characters |
| Max keywords per extension | 50 |
Keywords are matched as case-insensitive substrings. A keyword of "gg" matches "gg wp", "lol gg", and "gg".
Patterns
Regex patterns matched against chat messages. When a pattern matches, the handler receives the matched string.
{
"triggers": {
"patterns": ["https?://clips\\.twitch\\.tv/\\S+"]
}
}
Pattern validation rules
| Rule | Constraint |
|---|---|
| Max length | 200 characters |
| Max patterns per extension | 10 |
| Regex engine | Rust regex crate (linear-time, no backreferences) |
| ReDoS safety | Guaranteed by construction (Rust regex crate) |
Pattern matching runs in Rust on the bot side. JavaScript RegExp is never used for trigger matching. The matched substring is passed to the handler as the matched parameter:
import { pattern } from "@zaflun/lumio-sdk/server";
export const clipLogger = pattern(
"https?://clips\\.twitch\\.tv/\\S+",
async (ctx, message, matched) => {
// matched = "https://clips.twitch.tv/FunnyClip123"
return { reply: `Clip from ${ctx.user.name}: ${matched}` };
}
);
Events
Platform events that trigger handlers when they occur.
{
"triggers": {
"events": ["twitch:subscribe", "twitch:raid", "youtube:superchat"]
}
}
Each event type must be a valid Lumio event type. The handler receives the event payload:
import { event } from "@zaflun/lumio-sdk/server";
export const welcomeSub = event("twitch:subscribe", async (ctx, evt) => {
return { reply: `Welcome ${evt.user_name}!` };
});
Available event types
Twitch: twitch:follower, twitch:subscribe, twitch:gift_sub, twitch:resub, twitch:cheer, twitch:raid, twitch:reward, twitch:hype_train, twitch:hype_train_end, twitch:poll, twitch:poll_end, twitch:prediction, twitch:prediction_lock, twitch:prediction_end, twitch:goal, twitch:goal_end, twitch:ad_break, twitch:ban, twitch:unban, twitch:stream_online, twitch:stream_offline, twitch:stream_update
YouTube: youtube:subscribe, youtube:member, youtube:superchat, youtube:supersticker, youtube:gift_membership, youtube:gift_membership_received, youtube:poll
Kick: kick:follower, kick:subscribe, kick:gift, kick:stream_online, kick:stream_offline
Trovo: trovo:subscribe, trovo:spell
Spotify: spotify:track, spotify:play, spotify:pause, spotify:skip, spotify:volume, spotify:queue_add, spotify:device, spotify:playlist_add, spotify:playlist_remove, spotify:playlist_create, spotify:playlist_edit, spotify:playlist_delete
StreamElements: streamelements:tip
Chat: twitch:chat, youtube:chat, kick:chat, trovo:chat, chat:message
Timers
Recurring handlers that fire at a fixed interval.
{
"triggers": {
"timers": [
{
"name": "reminder",
"interval": 300,
"handler": "periodicReminder"
},
{
"name": "adDisclaimer",
"interval": 1800,
"handler": "adMessage"
}
]
}
}
Timer fields
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique identifier for this timer |
interval | integer | Yes | Firing interval in seconds (minimum: 60) |
handler | string | Yes | Name of the exported server function to call |
Timer validation rules
| Rule | Constraint |
|---|---|
| Minimum interval | 60 seconds |
| Handler | Must match an exported server function name |
Timer behavior
- Timers are per-install, not per-account. Two accounts installing the same extension have independent timers.
- Timer messages are sent to all declared platforms that the account has active bot connections for.
- If a platform bot is offline, the timer action for that platform is silently dropped. There is no retry — timers are best-effort.
- Users can override the interval and enable/disable individual timers in their dashboard.
Moderation
The moderate() handler does not need a trigger declaration — it runs on every message that passes built-in module checks. Declare it only in your handler code:
import { moderate } from "@zaflun/lumio-sdk/server";
export const linkFilter = moderate(async (ctx, message) => {
if (message.text.includes("banned-site.com")) {
return { block: true, action: "timeout", duration: 60, reason: "Banned link" };
}
return { block: false };
});
Moderation handlers use the sync execution path (500ms deadline). They require the chat:ban or chat:delete permission, which triggers enhanced manual review during store approval.