🪄 Spells
A Spell is a Discord slash command. When a user types /your-spell in Discord, it fires the linked Flow.
🔬Anatomy of a Spell
This is what a spell card looks like in your bot's Spells tab:
targetUSERrequiredamountINTEGERname/scoreThe /name Discord sees — lowercase, letters, digits, hyphens, underscores (max 32 chars).descriptionAdd points to a userShown in Discord's command picker. Required (max 100 chars).options[target, amount]Typed inputs the user fills in. Optional.requiredPermissions[]Discord permission flags. Empty = anyone can run this.// A Spell is a Discord slash command handler:
client.on("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "score") {
const target = interaction.options.getUser("target", true);
const amount = interaction.options.getInteger("amount") ?? 1;
// → your flow runs here
await db.run("... ON CONFLICT DO UPDATE SET points = points + ?", [target.id, amount]);
await interaction.reply(`Added ${amount} pts to ${target.username}! 🏆`);
}
});📥Spell Inputs (Options)
Options are the arguments Discord users fill in when invoking a spell. Discord validates them by type before the interaction reaches your bot.
| Discord type | Type int | Spark shape | Notes |
|---|---|---|---|
| STRING | 3 | string | Free text. Override sparkType to number/boolean to coerce. |
| INTEGER | 4 | number | Whole numbers. Supports minValue / maxValue. |
| BOOLEAN | 5 | boolean | True/false toggle. |
| USER | 6 | { id, username, displayName? } | User picker. Resolves to an object. |
| CHANNEL | 7 | { id, name? } | Channel picker. Object. |
| ROLE | 8 | { id, name? } | Role picker. Object. |
| NUMBER | 10 | number | Float. Supports minValue / maxValue. |
Options arrive as sparks: {{input.options.optionName}}. USER, CHANNEL, and ROLE options resolve to objects, so use {{input.options.target.id}} or {{input.options.target.username}}.
// Options are the arguments users fill in — Discord validates them:
const target = interaction.options.getUser("target", true); // type: USER, required
const amount = interaction.options.getInteger("amount") ?? 1; // type: INTEGER, optional
// USER options give you an object:
console.log(target.id); // → {{input.options.target.id}}
console.log(target.username); // → {{input.options.target.username}}
// Primitive options (STRING, INTEGER, NUMBER, BOOLEAN) are plain values:
// {{input.options.amount}} → amount🎯Fixed Choices
For STRING, INTEGER, or NUMBER options you can provide a fixed list. Discord restricts users to only these values.
levelSTRING (choices: Easy, Medium, Hard)requiredThe choice's display name is shown to the user in Discord. The choice's value is what arrives in {{input.options.level}}.
🔒Permission Gating
Spells can be restricted to members who hold specific Discord permissions. All listed flags are required (AND logic).
targetUSERrequiredreasonSTRINGBAN_MEMBERS| Flag | Who typically has it |
|---|---|
| KICK_MEMBERS | Moderators |
| BAN_MEMBERS | Senior moderators |
| MANAGE_CHANNELS | Admins |
| SEND_MESSAGES | Any member |
| MANAGE_MESSAGES | Moderators — delete/pin messages |
| MANAGE_ROLES | Admins |
| MODERATE_MEMBERS | Moderators — timeout members |
| ADMINISTRATOR | Server owner / admins |
🚫Reserved Input Names
These names can't be used as option names — they collide with built-in spark roots:
userguildchannelmessagememberinputoptionsvarssettingsbottriggersparkvar