Reference

Card template variables

A creator's card design stores its text as a template, so {Token} placeholders arrive in cardDesign.textLine1.content and textLine2.content unresolved. These 10 tokens are the complete set, and each row names the payload field you resolve it from yourself.

Velora already substitutes these.On our own overlays the tokens are replaced before render. You only need this page if you are building your own overlay and rendering a creator's cardDesign yourself — then the raw template text reaches you with the tokens intact, and you substitute from the payload fields below.

The variables

Matching is case-insensitive — {times}, {Times} and {TIMES}are the same token. Tokens that don't apply to a given event resolve to an empty string; unknown tokens are left exactly as written rather than silently removed.

TokenMeaningPayload fieldExample
{User}
also {Name}, {DisplayName}, {Viewer}
Display namedisplayNameCompTech
{Username}
also {Handle}
Username (@handle)usernamecomptech
{Reward}Reward namerewardTitle / itemNameDaily MoonPie
{Times}Redeem count (this user, this reward)counts.lifetime9
{Place}Ordinal place (1st / 2nd)builtInType1st
{Amount}Volts amountamount500
{Months}Subscription monthsmonths3
{Tier}Subscription tiertier2
{Viewers}Raid viewer countviewers42
{Message}Viewer's attached messageuserMessagehi chat!

Where cardDesign comes from

cardDesign is the creator's saved design from the Channel Points card designer. It is stored on the reward itself, so the same design arrives with every redemption of that reward until the creator edits it. Two different events carry one:

channel.channel_points_redemption

A viewer redeemed a channel-points reward. Carries counts.lifetime and builtInType, which is what {Times} and {Place} resolve from.

channel.stream_alert

Follow, sub, gift, resub, raid or Volts alert. Carries cardDesign plus a templateDataobject holding that alert type's own values (amount, months, tier, viewers, …).

A reward with no saved design sends alertMode: "default" and no cardDesign — render your own default rather than assuming the field is present.

Substituting it yourself

Do the whole template in one pass, not a chain of individual replacements. A chain re-scans text it has already substituted, so a viewer whose message literally contains {Reward} would have it expanded by a later step.

JavaScript — single-pass renderer
const VALUES = {
  user:     e.displayName || e.username,
  username: e.username,
  reward:   e.rewardName,
  times:    String(e.counts?.lifetime ?? 1),
  place:    e.builtInType === 'first' ? '1st'
          : e.builtInType === 'second' ? '2nd' : '',
  message:  e.userMessage || '',
};

const render = (tpl) =>
  tpl.replace(/\{([A-Za-z]+)\}/g, (whole, token) => {
    const v = VALUES[token.toLowerCase()];
    return v !== undefined ? v : whole;  // keep unknown tokens visible
  });

render(cardDesign.textLine2.content);

Leaving unknown tokens intact is deliberate — it surfaces a creator's typo instead of hiding it behind a blank space.

{Times} — the redeem count

counts.lifetime is the number of fulfilled redemptions of this reward by this viewer, all time, including the one being delivered. So a viewer's ninth redeem arrives as counts.lifetime: 9.

The field is omitted rather than sent as 0when we have no count — that lets you tell "no data" apart from "zero". If it is absent, render 1: the redemption in front of you is at least the first one.

Event payload — channel.channel_points_redemption (abridged)
// channel.channel_points_redemption (abridged)
{
  "userId": "…",
  "username": "comptech",
  "displayName": "CompTech",
  "rewardName": "Daily MoonPie",
  "rewardCost": 500,
  "userMessage": "hi chat!",
  "counts": { "lifetime": 9 },   // → {Times}
  "builtInType": null            // "first" | "second" → {Place}
}

Related