Files
Toju/docs-site/docs/plugin-development/api/commands.md
Myx ee293d7daf
Some checks failed
Deploy Web Apps / deploy (push) Successful in 5m52s
Build Android APK / build-android-apk (push) Failing after 23m15s
Queue Release Build / prepare (push) Successful in 1m42s
Queue Release Build / build-linux (push) Failing after 9m33s
Queue Release Build / build-windows (push) Successful in 26m5s
Queue Release Build / finalize (push) Has been skipped
feat: Rename to Toju and add translation
2026-06-05 17:17:29 +02:00

4.6 KiB

sidebar_position
sidebar_position
12

Slash Commands API

The Commands API lets plugins register / slash commands. When a user types / in the chat composer, Toju shows a Discord-style autocomplete menu of available commands. Selecting a command (click, Enter, or Tab) runs it — either immediately when it declares no options, or after the user types the requested arguments.

Required Capabilities

Method Capability
commands.register(id, command) ui.commands
commands.list() ui.commands

Every registration returns a disposable. Push it into context.subscriptions so the command is removed when the plugin unloads.

Command Scope

A command's scope controls where it appears:

Scope Available in
global (default) Chat servers and direct messages
server Only while a chat server is the active surface

Use global for commands that work without a server context (e.g. /help, /shrug). Use server for commands that act on the current server, channel, or members.

Options and Argument Parsing

Declare options to describe the arguments a command accepts. Toju parses what the user typed after the command name and passes the result to run as context.args, keyed by option name.

interface PluginApiSlashCommandOption {
  description?: string;
  name: string;
  required?: boolean;
  // 'rest' captures all remaining text; otherwise a single whitespace-delimited token
  type?: 'string' | 'number' | 'boolean' | 'rest';
}
  • Positional options are filled left-to-right from whitespace-delimited tokens.
  • A rest option captures all remaining text verbatim (use it last, for free-form text).
  • Missing positional values are passed as empty strings.
  • The autocomplete menu shows required options as <name> and optional ones as [name].

Values arrive as strings; convert number/boolean types yourself inside run.

Command Context

run receives a context that extends the standard action context (source: 'slashCommand') with the invocation details:

interface PluginApiSlashCommandContext extends PluginApiActionContext {
  args: Record<string, string>; // parsed values keyed by option name
  command: string;              // invoked name without the leading slash
  rawArgs: string;              // raw text typed after the command name
  // inherited: server, textChannel, voiceChannel, user, source
}

Register a Command

export function activate(context) {
  const api = context.api;

  // Server-scoped command with a free-form message argument.
  context.subscriptions.push(
    api.commands.register('announce', {
      name: 'announce',
      description: 'Post an announcement to the current channel',
      icon: '📢',
      scope: 'server',
      options: [{ name: 'message', type: 'rest', required: true }],
      run: (slash) => {
        api.messages.send(`📢 ${slash.args.message}`, slash.textChannel?.id);
      }
    })
  );

  // Global command that works in servers and DMs.
  context.subscriptions.push(
    api.commands.register('shrug', {
      name: 'shrug',
      description: 'Append the shrug emoticon',
      scope: 'global',
      run: () => api.messages.send('¯\\_(ツ)_/¯')
    })
  );
}

api.messages.send requires the messages.send capability, so the example above declares both ui.commands and messages.send in its manifest.

List Registered Commands

const allCommands = context.api.commands.list();

Returns every slash command currently registered across all active plugins, including their scope and options.

Built-in Commands

Toju ships first-party commands that are always available without any plugin, such as /lenny (posts ( ͡° ͜ʖ ͡°)). They appear in the same autocomplete menu tagged as Built-in. Plugin commands are listed alongside them; if a plugin registers a command with the same name as a built-in, both appear and the user can pick either.

How Input Is Handled

  • Typing / opens the menu; typing more characters filters by command name (prefix matches rank first).
  • Picking a command without options runs it immediately and clears the composer.
  • Picking a command with options fills /name so the user can type arguments, then Enter runs it.
  • Slash input is intercepted and never posted as a chat message. Text that starts with / but matches no registered command falls through and is sent as a normal message.