What a prefix command is
A prefix command is a pair of classes that reacts to a text message starting with a configurable character like ! or ?. When a user sends !roll 20, the framework detects the prefix, extracts the command name, parses the arguments, runs guards, and dispatches the message to the correct handler. This is the traditional Discord bot command model that predates slash commands, and it remains useful for fast operator workflows, internal moderation tooling, and bots where users already know command shortcuts.
The key difference between prefix commands and slash commands is what triggers them. Slash commands are triggered by Discord's built-in interaction system with autocomplete and validation. Prefix commands are triggered by raw text messages, which means the framework must handle parsing, argument coercion, and error reporting on its own. Some bots need both, and Spraxium lets you register prefix and slash commands in the same module without conflicts.
Prefix commands in Spraxium are not a legacy afterthought. They are full classes managed by the dependency injection container, registered in modules, and wired to the Discord client automatically during bootstrap. This means your command handlers can receive injected services, participate in the guard pipeline, benefit from cooldown tracking, and use the same exception handling that slash commands use.
When prefix commands still make sense
Prefix commands are still a strong fit for bots used by staff teams, internal tools, migration-heavy legacy communities, or workflows where fast typed shortcuts matter more than Discord's command picker UI. They are also useful when message-based habits already exist and moving everything to slash commands would add friction without much benefit.
They are a weaker fit when you need strong discoverability for casual users, rich option validation in the Discord client, or autocomplete-heavy input. In those cases, slash commands are usually the better default.
A basic command example
Here are two classes that show the two-class pattern. The first is a command definition that declares the name, aliases, and argument schema through @PrefixArg decorators. The second is a handler that receives the parsed arguments and the raw Message through @Ctx. Together, these cover the minimum structure you need to create a working prefix command.
import { PrefixCommand } from '@spraxium/common';
@PrefixCommand({
name: 'ping',
aliases: ['p'],
description: 'Check bot latency.',
category: 'Fun',
usage: '!ping',
cooldown: 3,
})
export class PingCommand {
build() {}
}Notice that the command class is purely declarative: it describes what the command is called, what aliases it accepts, and what cooldown it enforces. The handler class holds the execution logic and receives dependencies through decorators. For commands with arguments, you add @PrefixArg decorators to the command's build method and matching @PrefixArg parameter decorators on the handler's handle method. The next page covers argument schemas in detail.
Runtime path
At runtime, the flow is straightforward:
- The framework detects the configured prefix.
- It extracts the command name and tokenizes the remaining text.
- It resolves the command definition and parses arguments.
- It runs guards and cooldown checks.
- It invokes the registered handler.
That pipeline is why command definition and command execution are split into two classes. One class describes the schema; the other runs the logic against parsed values.
Registering commands in a module
Prefix commands do not activate just by existing in your source code. You need to register the command class in the module's commands array and the handler class in the handlers array so the framework knows about them and can wire them during bootstrap. Without both registrations, your command will either be invisible to the parser or have no handler to dispatch to.
import { Module } from '@spraxium/common';
import { PingCommand } from './commands/ping.command';
import { PingHandler } from './handlers/ping-command.handler';
@Module({
commands: [PingCommand],
handlers: [PingHandler],
})
export class FunModule {}The module must be imported by your root module or another module in the import chain. If a handler depends on a service, that service must be available in the module's provider scope or exported from an imported module, like any other injectable class. A typical prefix module organizes commands, handlers, and guards into their own folders inside the domain directory.
This structure keeps command definitions and handlers in dedicated folders and the module file at the domain root. As your bot grows, you can add more command and handler files without changing the folder structure. The module file is the single place that declares which commands and handlers are active.
Prefix commands also benefit from staying domain-oriented. A moderation module should own its moderation prefix commands, and an economy module should own its economy commands. That keeps aliases, guards, handlers, and supporting services close to the same business context.
Two classes per command
Each prefix command requires both a command class and a handler class registered in the same module. The command class declares structure and metadata. The handler class holds the execution logic and injectable dependencies. If you forget to register either one, the command will not work at runtime.
If you plan to support both prefix and slash commands, keep the business logic in shared services and let each command surface act as a thin entry point. That avoids duplicating the real behavior just because two command styles exist.