Spraxium logoSpraxium

Slash Commands Overview

Slash commands are the primary interaction model for modern Discord bots. Learn the decorator-driven architecture, the command-handler separation pattern, and the lifecycle that Spraxium uses to register and dispatch slash commands.

What slash commands are

Slash commands are the native interaction model that Discord introduced to replace unstructured text parsing. When a user types / in a text channel, Discord presents a searchable menu of commands the bot has registered, complete with typed parameters, descriptions, and inline validation. The user selects a command, fills in the options, and Discord delivers a structured ChatInputCommandInteraction to the bot process. The framework never has to parse raw message text, guess argument boundaries, or handle typos in command names because Discord itself enforces the schema before the interaction reaches the bot.

Spraxium implements slash commands as a decorator-driven, metadata-based subsystem with a strict separation between definition and execution. A definition class decorated with @SlashCommand declares the command schema, its name, description, options, permissions, and subcommand hierarchy. A separate handler class decorated with @SlashCommandHandler implements the business logic and receives typed parameter values through dependency injection. This separation means the schema can evolve independently from the handler logic, and a single command definition can have multiple handlers when subcommands are involved.

The slash command subsystem shares the same dependency injection container, guard pipeline, exception handler, and module system as every other part of the framework. Your handler classes can inject services, apply @UseGuards for permission checks, and benefit from structured error replies without any special configuration beyond registering commands and handlers in a module.

DI, guards, and exceptions work out of the box

Slash command handlers are full framework citizens. You can inject any registered service through the constructor, stack multiple guards with @UseGuards, and throw typed exceptions that the built-in exception handler converts into structured Discord replies. No extra wiring is needed beyond declaring the handler class inside a module.

Command-handler separation

The defining architectural decision of the slash command subsystem is that command schema and command logic live in separate classes. This is not an optional pattern; the framework enforces it through metadata keys. The @SlashCommand decorator writes schema metadata to the class, and the @SlashCommandHandler decorator writes execution metadata to a different class that references the command class. The runtime reads both metadata sets at module load time and wires them together in the registry.

This separation yields three practical benefits. First, the schema class becomes a pure declaration that can be imported by autocomplete handlers, test fixtures, and documentation generators without pulling in business logic or service dependencies. Second, the handler class has access to the full dependency injection container, so it can declare constructor parameters for services, repositories, and other providers that the DI container resolves automatically. Third, when a command uses subcommands, each subcommand gets its own handler class, keeping files small and focused.

import { SlashCommand, SlashOption } from '@spraxium/common';

@SlashCommand({
  name: 'ping',
  description: 'Check bot latency or ping a specific service.'
})
export class PingCommand {
  @SlashOption.String('service', {
    description: 'Service to check latency for',
    required: false,
    autocomplete: true
  })
  build() {}
}

Module registration

Commands and handlers are registered through the @Module decorator, using the commands array for definition classes and the handlers array for handler classes. The module loader reads these arrays during bootstrap, instantiates handler classes through the DI container, and wires them to their command definitions automatically.

modules/slash/slash.module.ts
import { Module } from '@spraxium/common';
import { PingCommand } from './commands/ping.command';
import { BanCommand } from './commands/ban.command';
import { PingHandler } from './handlers/ping.handler';
import { PingServiceAutocomplete } from './handlers/ping-service.autocomplete';
import { BanHandler } from './handlers/ban.handler';

@Module({
  commands: [PingCommand, BanCommand],
  handlers: [PingHandler, PingServiceAutocomplete, BanHandler],
})
export class SlashModule {}

The order of entries in the arrays does not matter. The framework uses metadata reflection to match handlers to their command definitions after all registrations are processed. If a handler references a command that is not in the commands array of any loaded module, the handler is silently ignored during binding.

Force-registering commands manually

During the normal boot sequence, the framework registers slash commands automatically after the client connects. In development mode it skips the REST call when commands have not changed since the last run, avoiding unnecessary Discord API calls. If you ever need to force a fresh registration without restarting the full bot, the application exposes forceRegisterSlashCommands for exactly that purpose.

This method pushes all current command payloads to the Discord REST API unconditionally. It is a no-op if the application has not finished booting or if no slash commands are registered. You can call it from a CLI script, an admin command handler, or any service that has access to the application instance.

src/main.ts
import { SpraxiumFactory } from '@spraxium/core';

const app = await SpraxiumFactory.create();
await app.listen();

// Force re-registration from an admin command or script
await app.forceRegisterSlashCommands();
project structure
ping.command.ts
ban.command.ts
config.command.ts
ping.handler.ts
ping-service.autocomplete.ts
ban.handler.ts
config-view.handler.ts
config-reset.handler.ts
config-automod.handler.ts
slash.module.ts

Place command definition files in a commands/ subfolder and handler files in a handlers/ subfolder within the module directory. Name handler files after the command or subcommand they handle with a .handler.ts suffix, and autocomplete handlers with an .autocomplete.ts suffix. The module file registers both arrays and serves as the single import point for the parent application module.

Decorator reference at a glance

DecoratorApplied toPurposeCovered in
@SlashCommand(config)Class

Declares a command name, description, permissions, and flags.

Definition and Options
@SlashOption.String(), Integer(), etc.Method

Adds a typed option to the command or subcommand schema.

Definition and Options
@SlashSubcommand(meta)Method

Turns a method into a subcommand within the command class.

Subcommands and Groups
@SlashSubcommandGroup(meta)ClassDeclares a class as a subcommand group container.Subcommands and Groups
@SlashSubcommandGroups(groups)ClassRegisters group classes on a command class.Subcommands and Groups
@SlashCommandHandler(command, options?)Class

Marks a class as the execution handler for a command or subcommand.

Handlers and Injection
@SlashOpt(name)Parameter

Injects the resolved option value into a handler parameter.

Handlers and Injection
@Ctx()Parameter

Injects the raw ChatInputCommandInteraction or AutocompleteInteraction.

Handlers and Injection
@SlashAutocompleteHandler(command, optionName)Class

Marks a class as the autocomplete handler for a specific option.

Autocomplete
@SlashFocused()Parameter

Injects the partial string the user has typed into the focused option.

Autocomplete