K
Docs
Building Bots

🪄 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:

🪄
/score
Add points to a user
Spell
Inputs
targetUSERrequired
amountINTEGER
name/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.
📐Under the hood — a spell is a slash command handler
TypeScript
// 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 typeType intSpark shapeNotes
STRING3stringFree text. Override sparkType to number/boolean to coerce.
INTEGER4numberWhole numbers. Supports minValue / maxValue.
BOOLEAN5booleanTrue/false toggle.
USER6{ id, username, displayName? }User picker. Resolves to an object.
CHANNEL7{ id, name? }Channel picker. Object.
ROLE8{ id, name? }Role picker. Object.
NUMBER10numberFloat. Supports minValue / maxValue.
💡Accessing options in flows

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}}.

📐Under the hood — options are just function arguments
TypeScript
// 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.

🪄
/difficulty
Pick a difficulty level
Spell
Inputs
levelSTRING (choices: Easy, Medium, Hard)required

The 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).

🪄
/ban
Ban a member from the server
Spell
Inputs
targetUSERrequired
reasonSTRING
Required permissions
BAN_MEMBERS
FlagWho typically has it
KICK_MEMBERSModerators
BAN_MEMBERSSenior moderators
MANAGE_CHANNELSAdmins
SEND_MESSAGESAny member
MANAGE_MESSAGESModerators — delete/pin messages
MANAGE_ROLESAdmins
MODERATE_MEMBERSModerators — timeout members
ADMINISTRATORServer owner / admins

🚫Reserved Input Names

These names can't be used as option names — they collide with built-in spark roots:

userguildchannelmessagememberinputoptionsvarssettingsbottriggersparkvar