🧩 Pieces
Every Piece on the Canvas — documented with config fields, output ports, sparks they add, JSON examples, and a TypeScript equivalent so you understand exactly what each one does.
Messages
base.replyTalks back! On spells, replies under the slash command (optionally private). On message triggers, replies in a thread under the message that fired the flow.
Base Setbase.followupSends an additional message under the same slash command. Discord only allows one initial reply per spell — use Reply for the first message, then Follow-up for every message after.
Base Setbase.sendDrops a message anywhere in your server. Unlike Reply, it doesn't need to be a response to anything — it can post in #announcements while the trigger happened in #general.
Base Setbase.embedSends a fancy embed card with a title, body text, and accent color. Embeds stand out in chat and look way more polished than plain text. Great for leaderboards, announcements, and summaries.
Base Setbase.dmSends a private DM directly to a specific user. No one else sees it. Perfect for sending private results, warnings, or personalized info. Note: some users have DMs disabled — that's what the Error port is for.
Base SetLogic
base.conditionThe decision maker! Checks if something is true or false, then sends the flow down one of two paths. It's how your bot becomes smart — 'if this user is an admin, do X, otherwise do Y'.
Base Setbase.mergeA visual hub for converging flows. Wire both Condition outputs (or any parallel paths) into this piece, then connect one output to your shared downstream chain. The runner already supports multiple incoming edges — Merge makes that pattern obvious on the canvas.
Base Setbase.coalesceSets a spark from a primary template, or falls back when the primary is empty. Perfect for /profile user_p: use the mentioned user's ID when provided, otherwise the invoker's ID — without duplicating Save Sparks on each branch.
Base Setbase.waitPumps the brakes! Pauses the flow for a set number of milliseconds before continuing. Useful for adding a dramatic pause before a big reveal, or spacing out multiple messages so they don't all appear at once.
Base Setbase.variableCreates or updates a named spark that holds a typed value — text, number, yes/no, list, or object. Update existing sparks in place: numbers use Add/Subtract/Multiply/Divide, text uses append/prepend, lists use Add to list / Prepend to list (single items or rows), booleans support toggle. The spark is then available everywhere downstream.
Base Set{{score}}base.loopRepeat after me! Takes a list and runs the connected flow once for each item in it. Great for sending messages to multiple users, processing a list of scores, or doing anything that needs to happen more than once.
Base Set{{item}}base.returnEnds the custom piece's flow and hands named values back to whoever called it. The fields shown here come from the returns you defined on this custom piece.
Base SetVault
base.db.readFetches rows from one of your Vault tables. Pick a table, choose which column to match on (like user_id), and give the result a name — it becomes a ✨ Spark you can drop into any later piece.
Base Set{{result}}{{result.length}}{{result.0}}base.db.writeSaves a row to one of your Vault tables. Pick a table, fill in each column's value, and optionally turn on 'update if exists' to avoid duplicate rows — perfect for tracking coins, XP, or anything per-user.
Base Setbase.db.deletePermanently removes rows from one of your Vault tables. Pick the table, choose which column to match on, and only those rows get deleted. No filter? The whole table is wiped — so be careful!
Base SetUtility
base.randomReaches into a bag and pulls out one random item. Great for random jokes, quotes, prizes, challenge assignments, or anything where variety is the spice of life.
Base Set{{pick}}base.logWrites a message to your bot's Activity log. Use info for breadcrumbs, warn for odd-but-okay moments, and error when something went wrong but you handled it in the flow. Great for debugging without spamming Discord.
Base Setbase.formatConstructs a piece of text by filling in a template with live values. Combine static text with Sparks to build dynamic messages, labels, or anything that needs to be assembled from parts.
Base Set{{formatted}}base.format_timeFormats a date/time spark into human-readable text, ISO, a custom pattern, or Discord's native `<t:unix:style>` timestamps (t, T, d, D, f, F, R). Leave the input empty to use the current time when the flow runs.
Base Setbase.mathRuns an arithmetic expression and stores the answer as a Spark. Add scores, calculate percentages, multiply rewards — if numbers are involved, this piece does the work.
Base Set{{total}}base.sortTakes a list and sorts it ascending or descending. If the list came from a Vault read, it auto-detects the table schema and lets you pick which column to sort by — no guessing field names. The sorted result becomes a ✨ Spark you can loop over or show directly.
Base Set{{sorted}}{{sorted.length}}{{sorted.0}}base.text.splitSplits text into a list using a delimiter — comma, space, newline, or any string. Great for parsing command arguments, CSV-style data, or multi-line input.
Base Set{{parts}}{{parts.length}}{{parts.0}}base.text.joinJoins list items into a single string with a separator between each item. Perfect for building comma-separated lists, bullet lines, or one big message from many sparks.
Base Set{{joined}}base.text.replaceReplaces every occurrence of a find string with another value. Handy for sanitizing input, swapping placeholders, or cleaning up user text.
Base Set{{replaced}}base.text.transformTransforms text casing or whitespace. Use trim before comparing strings, uppercase for shouty replies, or title case for display names.
Base Set{{transformed}}base.text.lengthReturns how many characters are in a piece of text. Useful for validating message length, word-count style games, or limits before sending.
Base Set{{length}}base.list.lengthReturns how many items are in a list spark. Pair with Condition to branch on empty lists or check leaderboard size.
Base Set{{count}}base.list.getReturns the item at a zero-based index — 0 is first, 1 is second, and so on. Great for showing the top scorer or a random pick you already stored.
Base Set{{row}}base.list.sliceReturns a slice of a list from a start index up to (but not including) an end index. Use it for top-10 boards, paging, or trimming edges.
Base Set{{slice}}{{slice.length}}{{slice.0}}base.list.uniqueDeduplicates a list while keeping the first occurrence of each value. Handy after merges, user submissions, or tag lists.
Base Set{{unique}}{{unique.length}}{{unique.0}}base.list.containsChecks whether a list contains an item (string match). Routes to Found when present, Empty when not — like a mini condition for lists.
Base SetChannels
User
base.member.has_permissionBranches based on whether a server member has a specific Discord permission (like Ban Members or Manage Messages). Use it in message flows to skip filters for mods, or to gate logic on someone other than the command invoker.
Base Setbase.role.addHands out a Discord role to a user. Perfect for level-up rewards, verifications, unlock systems, or any time someone earns access to something. The bot needs Manage Roles permission for this to work.
Base Setbase.role.removeThe flip side of Add Role — takes a role away from a user. Use it for role rotation, timed access, punishments, or when someone no longer meets the criteria for a role.
Base Setbase.member.fetchFinds a member of your server by their Discord user ID and loads their info — username, roles, join date, and more — as a Spark you can use anywhere. Useful when you need data about someone other than whoever triggered the flow.
Base Set{{member}}{{member.name}}Moderation
mod.kickRemoves a member from the server — they can still rejoin with an invite. A lighter touch than a ban; great for first offences or automated flows. Always pair with a Condition to confirm the boot is warranted.
The Mod Squadmod.banPermanently bans a user. Unlike Kick, they cannot rejoin. Optionally deletes their recent messages (1–7 days). Use with care — you can always Unban later if needed.
The Mod Squadmod.unbanRemoves a ban by user ID. They won't automatically rejoin — they'll need a fresh invite. Perfect for /unban commands where a mod reinstates a previously banned member.
The Mod Squadmod.timeoutPuts a member in Discord's native timeout. While timed out they can't send messages, add reactions, or join voice. Perfect for automated cool-downs. Requires Moderate Members permission. Max duration is 28 days.
The Mod Squadmod.untimeoutClears an active timeout and restores a member's ability to chat. Handy for manual override commands — a mod can un-mute someone before their timeout would naturally expire.
The Mod Squadmod.delete_messageDeletes one specific message from a channel. Use in message-event flows where bad content needs to go immediately. The message ID and channel ID are available as Sparks in message triggers.
The Mod Squadmod.purgeDeletes up to 100 messages from a channel in one sweep. Discord only allows bulk-deleting messages under 14 days old. Perfect for cleaning up spam raids, accidental leaks, or bot noise.
The Mod Squadmod.slowmodeSets a channel's slowmode cooldown — from 0 (off) to 21600 seconds (6 hours). Users can only send one message per cooldown. Great for calming heated chats, preventing raid spam, or managing high-traffic events.
The Mod Squadmod.lock_channelRemoves @everyone's Send Messages permission in a channel — effectively locking it. The channel remains visible and readable; nobody can post until you Unlock it. Great for announcements-only mode or emergency lockdowns.
The Mod Squadmod.unlock_channelRe-enables @everyone's Send Messages permission, reversing a Lock Channel. When the dust settles, unlock and get the conversation flowing again.
The Mod Squad