Slash Commands
Triggered by / in the message input. The TipTap suggestion API powers the picker; definitions live in SlashCommandDefinitionMap.
Command reference
| Command | Kind | Parameters | Result |
|---|---|---|---|
/flip | Immediate client | — | Posts 🌝 **Heads** or 🌚 **Tails** |
/me | Immediate client | message (required) | Posts *message* (italic emphasis) |
/roll | Immediate client | — | Posts 🎲 Rolled a **N** (1–100) |
/shrug | Immediate client | text (optional) | Posts text¯\_(ツ)_/¯ |
/tableflip | Immediate client | — | Posts (╯°□°)╯︵ ┻━┻ |
/unflip | Immediate client | — | Posts ┬─┬ノ( º _ ºノ) |
/topic | Immediate tRPC | text (optional) | Calls room.updateRoom to set/clear the topic |
/poll | Dialog → tRPC | — | Opens the poll dialog; on submit posts a MessageType.Poll message |
/remind | Dialog → tRPC | — | Opens the scheduled-job dialog → scheduled messages |
/schedule | Dialog → tRPC | — | Opens the scheduled-job dialog → scheduled messages |
How it works
Adding a command touches three places (per the registry pattern): the SlashCommandType enum, its SlashCommandDefinitionMap entry (icon, title, description, parameters[], type), and an arm in useExecuteSlashCommand.
All execution happens in useExecuteSlashCommand via a switch on SlashCommandType — the definition is static metadata only, with no execute() method. Three execution paths:
- Client-only (
Flip,Me,Roll,Shrug,TableFlip,Unflip) — build thesendMessageinput inline and fall through to the normalsendMessage. - Server call (
Topic) — call a tRPC mutation directly (room.updateRoom), no message posted by the command itself. - Dialog (
Poll,Remind,Schedule) — open a dialog that owns its own submit, so richer inputs use normal form controls instead of inline parameter chips.
Typing a command
flowchart TD
A([User types /]) --> B[TipTap suggestion activates]
B --> C{User picks command}
C -->|no parameters| D[useExecuteSlashCommand immediately]
C -->|has parameters| E[SlashCommandSuggestion.command fires]
E --> F[Read remaining editor text after range]
F --> G[Delete from range.from to doc end]
G --> H{remainingText?}
H -->|yes| I[parseTextAndParameters → restore parameterValues + trailingMessage]
H -->|no| J[empty parameterValues]
I --> K[setPendingSlashCommand + parsed values]
J --> K
K --> L[SlashCommandParameters UI shows with chips pre-filled]
L --> M([User fills in + submits])
M --> D
Parameter mode ↔ text serialisation
Collapsing parameter mode back to text (Escape, or Backspace in an empty command/trailing input) rebuilds the input as:
/CommandType parameterName1|value1 parameterName2|value2 trailingMessage
ID_SEPARATOR (|) separates a parameter name from its value; parameters are space-separated; the last parameter's value is greedy; re-parsing uses prefix matching per parameter in definition order. The RichTextEditor remounts with the formatted text and focuses at the end.
Key files
| File | Role |
|---|---|
apps/web/app/models/message/slashCommands/SlashCommandType.ts | command enum |
apps/web/app/services/message/slashCommands/SlashCommandDefinitionMap.ts | Record<SlashCommandType, SlashCommand> — static definitions |
apps/web/app/composables/message/slashCommand/useExecuteSlashCommand.ts | execution switch |
apps/web/app/composables/message/slashCommand/useSlashCommandExtension.ts | TipTap extension (mirrors MentionExtension) |
apps/web/app/services/message/slashCommands/SlashCommandSuggestion.ts | picker suggestion config |
apps/web/app/services/message/slashCommands/parseTextAndParameters.ts | text → parameterValues re-parsing |
apps/web/app/store/message/input/slashCommand.ts | pendingCommand + parameterValues + trailingMessage store |
apps/web/app/components/Message/Model/Message/Input/ActionsMenuButton.vue | the composer's + menu over the dialog commands |
Notes
The three commands that open a dialog rather than send something — /poll, /schedule and /remind — are also the composer's + menu, which is Discord's home for creating a poll. The menu runs the same useExecuteSlashCommand with the same type and takes each entry's icon from SlashCommandDefinitionMap, so a command and its menu entry cannot drift; only the entry's wording differs, because a command's title names the command (/poll) where a menu of actions wants a verb.
The send pipeline is untouched by commands: sendComposerMessage, RichTextEditor, and the mention suggestion know nothing about slash commands. Client-only commands go through the same sendMessage as a hand-typed message.