Skip to main content

Automation Templates

Template variables let you weave live data — the viewer's name, the current song, the stream title, a custom variable you set — into any bot command response, automation action, or timed message. The templating engine is tiny and predictable: you wrap a variable name in double curly braces, and Lumio replaces it with the matching value at runtime.

Who needs this guide?

  • Anyone writing bot commands and wanting the response to include the caller's name.
  • Anyone building automation actions that send chat messages, Discord messages, or tips announcements.
  • Timed-message authors who want dynamic content ("Currently playing \{\{songName\}\} on \{\{platform\}\}").
  • Anyone debugging a template that resolves to an empty string or leaks the literal \{\{name\}\} into chat.

Where Templates Are Used

The same engine powers every string field in the app that promises "variables are supported":

  • Bot commands — the response body of !command entries (cross-platform and per-platform overrides).
  • Automation actions — the message field of "Send chat message", "Send Discord message", "Shout out user", and similar action nodes.
  • Timed messages — bot modules that post periodic messages.
  • Welcome messages / subscriber greetings — the body of automated greetings.

If a field says it accepts templates, it uses the exact rules below.

Syntax: \{\{variable\}\}

Variables are wrapped in double curly braces:

Hello {{displayName}}, welcome to the stream!

The parser uses the regex \{\{(\w+(?:\.\w+)*)\}\}, which means:

  • The name must match \w+ (letters, digits, underscore) — camelCase is the convention.
  • Dotted paths are allowed: \{\{event.type\}\}, \{\{discord.serverName\}\}, \{\{var.myCustomVar\}\}, \{\{event.raw.someField\}\}.
  • Single curly braces (\{name\}) are not interpreted as variables — they are left untouched. Some older Lumio documents show single braces for illustrative purposes; the actual engine requires double braces.
  • Replacement is done left-to-right in one pass. The resolved value is not re-parsed, so a variable whose value happens to contain \{\{otherVar\}\} will appear literally.

Quick Start

TemplateTypical output
Welcome {{displayName}}!Welcome Rapha!
{{username}} cheered {{cheerAmount}} bits!rapha cheered 500 bits!
Now playing: {{songName}} by {{songArtist}}Now playing: Blinding Lights by The Weeknd
Stream: {{streamTitle}} ({{category}})Stream: Valorant Ranked (Valorant)
Uptime: {{uptime}} / {{liveViewerCount}} viewersUptime: 2h 30m / 1234 viewers
Shout out to {{var.lastRaider}}!Shout out to someStreamer!
Order #{{orderNumber}} — {{orderTotal}} {{orderCurrency}}Order #1082 — 42.00 EUR

Resolution Rules

There are three cases, and knowing which applies saves a lot of debugging time.

  1. Known variable, data present, permission granted → replaced with the value.
  2. Known variable, data missing or permission denied → replaced with an empty string. The rest of the message still renders, which is often why you see weird double-spaces (" " where you expected "{{someVar}}").
  3. Unknown variable (typo, not a real variable) → left as-is, braces and all: Hello {{unknwn}}! stays Hello {{unknwn}}!. This is deliberate so you can spot typos in output.

The empty-string-on-missing-data rule is what lets you write defensive templates like:

Currently playing {{songName}}{{songArtist}}

If Spotify isn't connected, nothing renders for those variables. If it is, you get Blinding LightsThe Weeknd (you'll want to add a separator — see Tips).

Permission Gating

Each variable category has a permission requirement. If the executing context lacks the permission, the variable resolves to an empty string (rule 2 above).

Variable groupRequired permission
Channel, Bot, Time, Shopify, OBS, Discord, Custom (var.*)none
User (username, displayName, avatarUrl, userId)chat:userinfo
Stream, Channel Status, Follower, Sub, Cheer, Raid, Redemption, Tip, Hype Train, Poll, Prediction, Event metadataevents:read
Spotify (songName, songArtist, …)spotify:read

Bot contexts run with the account's permissions, not the calling viewer's — so a chat command defined on an account that lacks spotify:read will never resolve \{\{songName\}\}, even if Spotify is connected. Grant the permission to the role assigned to the account's bot, or add it to your Owner/Administrator role.

Writer's convenience: automations save-time scanning. When you save an automation or bot command that uses \{\{songName\}\}, Lumio runs required_permissions() against your template and can warn you which permissions the executing account needs.

Variable Reference

Always available (no permission required)

Channel

VariableDescription
\{\{channelName\}\}Display name of the channel.
\{\{channelUrl\}\}URL to the channel page.
\{\{platform\}\}Platform slug (twitch, youtube, kick, trovo).

Bot

VariableDescription
\{\{botName\}\}Bot account's username.
\{\{commandPrefix\}\}Current command prefix (for example !).

Time

VariableDescription
\{\{date\}\}Current date in the configured timezone.
\{\{time\}\}Current time.
\{\{timestamp\}\}Unix timestamp.
\{\{timezone\}\}Configured timezone name.

Shopify (from Shopify order events)

VariableDescription
\{\{orderNumber\}\}Order number.
\{\{orderTotal\}\}Order total.
\{\{orderCurrency\}\}ISO currency code.
\{\{productName\}\}Product name from the order.

OBS (from the OBS integration)

VariableDescription
\{\{sceneName\}\}Current scene.
\{\{bitrate\}\}Current streaming bitrate.
\{\{fps\}\}Current frames per second.
\{\{droppedFrames\}\}Dropped frames counter.

Discord (dotted paths)

VariableDescription
\{\{discord.serverName\}\}Discord server display name.
\{\{discord.channelName\}\}Discord channel name.
\{\{discord.memberCount\}\}Member count at event time.

Custom variables (var.*)

User-defined variables you set via the automation builder or the custom-variables settings page. There is no hard list — any name under var.* looks up the matching entry.

VariableDescription
\{\{var.anyName\}\}Value of the named custom variable, or empty string if unset.

Requires chat:userinfo

Context: info about the user who triggered the event (the viewer who followed, subscribed, sent the bits, ran the command, etc.). If the bot account lacks chat:userinfo, these resolve to empty strings.

VariableDescription
\{\{username\}\}Login / handle.
\{\{displayName\}\}Display name (may include capitalization / unicode).
\{\{avatarUrl\}\}Profile picture URL.
\{\{userId\}\}Platform user ID.

Requires events:read

Stream snapshot:

VariableDescription
\{\{streamTitle\}\}Current stream title.
\{\{category\}\}Current category/game.
\{\{gameName\}\}Legacy alias for \{\{category\}\} — kept for backward compatibility.
\{\{viewerCount\}\}Viewer count at event time.
\{\{uptime\}\}Stream uptime.
\{\{streamUrl\}\}Link to the live stream.
\{\{streamPreviewUrl\}\}URL of the preview thumbnail image.

Channel-status (multi-platform live state):

VariableDescription
\{\{isLive\}\}true / false.
\{\{liveViewerCount\}\}, \{\{liveCategory\}\}, \{\{liveTitle\}\}, \{\{livePlatform\}\}Current live data aggregated across connected platforms.

Follower:

VariableDescription
\{\{followerName\}\}Display name of the new follower (alias for \{\{displayName\}\} scoped to follower events).

Subscription:

VariableDescription
\{\{subTier\}\}Tier 1, Tier 2, Tier 3, or the platform equivalent.
\{\{subMonths\}\}Cumulative subscription months.
\{\{isGift\}\}true / false.
\{\{giftRecipient\}\}Display name of the gift-sub recipient (only when isGift=true).

Cheer / bits:

VariableDescription
\{\{cheerAmount\}\}Bits cheered.
\{\{cheerMessage\}\}Message sent with the cheer.

Raid:

VariableDescription
\{\{raidViewers\}\}Number of incoming raiders.
\{\{raidFrom\}\}Name of the raiding channel.

Channel-point redemption:

VariableDescription
\{\{rewardName\}\}Reward title.
\{\{rewardCost\}\}Channel-point cost.
\{\{rewardInput\}\}Viewer-supplied text, if the reward requires input.

Tip / donation:

VariableDescription
\{\{tipAmount\}\}Numeric amount.
\{\{tipCurrency\}\}ISO currency code.
\{\{tipMessage\}\}Viewer's tip message.

Hype train:

VariableDescription
\{\{hypeTrainLevel\}\}Current level.
\{\{hypeTrainTotal\}\}Total contributions so far.
\{\{hypeTrainProgress\}\}Progress toward the next level.

Poll / prediction:

VariableDescription
\{\{pollTitle\}\}, \{\{pollChoices\}\}, \{\{pollWinner\}\}Live poll state.
\{\{predictionTitle\}\}, \{\{predictionOutcomes\}\}, \{\{predictionWinner\}\}Live prediction state.

Event metadata (dotted paths):

VariableDescription
\{\{event.type\}\}Event type, e.g. twitch:follower, youtube:superchat.
\{\{event.platform\}\}Source platform slug.
\{\{event.raw.<field>\}\}Any raw field of the source event — useful for platform-specific data not yet surfaced as a first-class variable.

Requires spotify:read

VariableDescription
\{\{songName\}\}Current track title.
\{\{songTitle\}\}Legacy alias for \{\{songName\}\}.
\{\{songArtist\}\}Track artist(s).
\{\{songAlbum\}\}Album name.
\{\{songUrl\}\}Spotify track URL.
\{\{songAlbumArt\}\}Album art image URL.
\{\{albumArt\}\}Legacy alias for \{\{songAlbumArt\}\}.
\{\{songDuration\}\}Track duration.
\{\{songProgress\}\}Current playback position.
\{\{isPlaying\}\}true / false.
\{\{shuffleState\}\}true / false.
\{\{repeatState\}\}off, track, or context.
\{\{spotifyDevice\}\}Active playback device name.
\{\{spotifyPlaylist\}\}Active playlist name, if any.

Spotify must actually be connected for these to have values — the permission alone is not enough.

Recipes

Welcome message with username

Bot command body:

Welcome to the stream, {{displayName}}! Enjoy your stay :)

Requirements: chat:write (to send), chat:userinfo (to resolve \{\{displayName\}\}).

Shoutout template

Go check out {{var.lastRaider}} at https://twitch.tv/{{var.lastRaider}} — they raided us with {{raidViewers}} viewers!

Requirements: chat:write, events:read (for \{\{raidViewers\}\}). \{\{var.lastRaider\}\} is whatever value you stored in the custom variable lastRaider (typically via a "Set Variable" automation action on the raid trigger).

Now-playing command

🎵 Now playing: {{songName}} — {{songArtist}} [{{spotifyDevice}}]

Requirements: chat:write, spotify:read. When Spotify is paused or disconnected, the fields resolve to empty strings, producing 🎵 Now playing: — [] — so guard it:

🎵 {{isPlaying}} · {{songName}} — {{songArtist}}

or use a conditional automation step that only sends the message when \{\{isPlaying\}\} equals true.

Sub announcement

{{displayName}} just subscribed at {{subTier}} for {{subMonths}} months! 🎉

Requirements: chat:write, chat:userinfo, events:read.

Tip thank-you (with currency)

Thank you {{displayName}} for the {{tipAmount}} {{tipCurrency}} tip! Your message: "{{tipMessage}}"

Requirements: chat:write, chat:userinfo, events:read.

Reading an uncommon event field

Every event has a raw map containing platform-native fields. If Lumio doesn't expose a first-class variable for what you need, reach into event.raw:

New Kick follow from {{event.raw.user_slug}}

Requirements: events:read. Use this as a fallback; first-class variables should be preferred for portability across platforms.

Random-user picker

Lumio doesn't ship a \{\{random\}\} built-in. The idiomatic pattern is to use an automation action to pick a random value, store it in a custom variable, then reference \{\{var.pickedUser\}\} in the next step's template. Custom variables have no permission gate.

Tips & Best Practices

  • Always add separators. Missing data resolves to empty string, so \{\{songName\}\} by \{\{songArtist\}\} becomes by when Spotify is paused. A small conditional upstream avoids the awkward output.
  • Prefer \{\{displayName\}\} over \{\{username\}\} for user-facing messages — displayName preserves capitalization and localized characters; username is the raw handle.
  • Use \{\{event.raw.*\}\} sparingly. It ties your template to a specific platform's payload. When a field starts mattering to more than one command, request a first-class variable.
  • Custom variables are your escape hatch. Anything not covered by a built-in (random picks, accumulated counters, last caller) can live in \{\{var.*\}\} and be updated by an automation step.
  • Test with the preview. The bot-command and automation editors render a preview with sample context so you can see exactly which variables resolve.
  • Legacy aliases exist for compatibility. \{\{songTitle\}\} = \{\{songName\}\}, \{\{albumArt\}\} = \{\{songAlbumArt\}\}, \{\{gameName\}\} = \{\{category\}\}. Prefer the canonical names in new templates.

Troubleshooting

My template output contains literal \{\{name\}\}

That's the "unknown variable" rule. Either the name is a typo (\{\{usrname\}\} instead of \{\{username\}\}), or it doesn't exist (\{\{viewerName\}\} isn't a real variable — use \{\{displayName\}\}). Cross-check the Variable Reference above.

The output is missing where I expected data

The variable is known but resolved to empty string. Possible causes, in order:

  1. The variable category isn't populated for this trigger. A follower event doesn't populate \{\{cheerAmount\}\} — it's simply absent.
  2. Permission missing. The bot account's role doesn't include spotify:read / events:read / chat:userinfo. Fix: add the permission to the role, or assign a role that already has it.
  3. Source feature not connected. \{\{songName\}\} needs an active Spotify connection; \{\{sceneName\}\} needs OBS connected.

Why isn't \{\{name\}\} (single braces) working?

Lumio's engine only recognizes double braces. Single braces are left alone so that message content containing JSON or code snippets isn't accidentally rewritten. Convert \{name\} to \{\{name\}\}.

Can I nest variables?

Not directly. The replacement pass doesn't recurse. If you need composition, pre-compute the value into a custom variable with an automation step, then reference \{\{var.composed\}\} in the final template.

The preview shows the right value but chat shows empty

Preview uses a sample context; chat uses the live one. If preview renders \{\{songName\}\} but chat shows empty, Spotify isn't playing at that moment — or you lack spotify:read on the bot's account.

\{\{event.raw.someField\}\} is always empty

Either the field name doesn't exist in this event's raw payload (platform-specific), or you lack events:read. Check a sample event under Dashboard → Events — click a row and inspect the raw fields.

  • Roles & Permissions — which role grants which variable's permission.
  • Automations — triggers, actions, and the automation builder.
  • Bot Commands — where most templates live.
  • Chat — how to send chat messages from the dashboard.