The command definition class
A slash command definition is a class decorated with @SlashCommand that declares what Discord should show in the command picker. The class itself contains no execution logic. Its purpose is to hold metadata that the SlashRegistrar reads at startup to build the JSON payload pushed to the Discord REST API. Options are declared as method decorators on a build() method, and subcommands are declared on named methods. The class can be imported by handlers, autocomplete handlers, and test fixtures without pulling in service dependencies.
The @SlashCommand decorator accepts a SlashCommandConfig object with the following properties. Every command must provide name and description at minimum. The remaining properties control permission defaults, DM availability, NSFW flagging, guild-specific registration, and localization.
import { SlashCommand, SlashOption } from '@spraxium/common';
import { PermissionFlagsBits } from 'discord.js';
@SlashCommand({
name: 'ban',
description: 'Ban a member from the server.',
defaultMemberPermissions: PermissionFlagsBits.BanMembers,
})
export class BanCommand {
@SlashOption.User('target', {
description: 'The member to ban',
required: true,
})
@SlashOption.String('reason', {
description: 'Reason for the ban',
required: false,
maxLength: 512,
})
@SlashOption.Integer('days', {
description: 'Number of days of messages to delete',
required: false,
min: 0,
max: 7,
})
build() {}
}Option types
Spraxium provides nine option type decorators through the SlashOption namespace, one for each Discord application command option type. Each decorator is a method decorator applied to the build() method of the command class (or to a @SlashSubcommand method when the option belongs to a subcommand). Options are stacked on the same method, and the order of decorators determines the display order in the Discord client (top decorator appears last in the picker).
Every option decorator accepts two positional arguments: the option name (string, used for Discord registration and @SlashOpt matching) and a config object. The config object extends SlashBaseOptionConfig with type-specific fields.
Base option configuration
All nine option types share a common configuration interface. The description field is the only required property. The remaining properties have sensible defaults so you only need to provide the ones relevant to your use case.
| Property | Type | Default | Description |
|---|---|---|---|
| description | string | (required) | Description shown to the user in the Discord option picker. 1-100 characters. |
| required | boolean | false | Whether the user must provide a value. When false, the handler receives null if the option is omitted. |
| i18n | SlashI18nKeys | undefined | Localization keys for option name and description, resolved through the i18n module. |
String option constraints
The @SlashOption.String decorator supports four additional configuration properties beyond the base config. These properties map directly to the Discord API constraints for string options and let you control input length, provide fixed choices, or enable autocomplete suggestions.
import { SlashCommand, SlashOption } from '@spraxium/common';
@SlashCommand({
name: 'echo',
description: 'Repeat a message back to the channel.',
})
export class EchoCommand {
@SlashOption.String('message', {
description: 'Text to echo',
required: true,
minLength: 1,
maxLength: 2000,
})
build() {}
}When minLength or maxLength is set, Discord validates the input length client-side before the interaction is sent. When choices is provided, the user selects from a dropdown instead of typing freely. When autocomplete is set to true, Discord calls your autocomplete handler as the user types, providing real-time dynamic suggestions from your code.
choices and autocomplete are mutually exclusive
Combining choices and autocomplete on the same option is not allowed by the Discord API. When autocomplete is enabled, the static choices are ignored and only your autocomplete handler is consulted. Use static choices for small, fixed lists and autocomplete for dynamic or large value sets.
Numeric option constraints
Both @SlashOption.Integer and @SlashOption.Number support min and max properties that set the allowed value range. Discord enforces these constraints on the client side, so the handler is guaranteed to receive a value within the specified range (or null if the option is not required and the user omits it). The Integer variant also supports choices and autocomplete, while the Number variant supports autocomplete only.
import { SlashCommand, SlashOption } from '@spraxium/common';
@SlashCommand({ name: 'roll', description: 'Roll a dice.' })
export class RollCommand {
@SlashOption.Integer('sides', {
description: 'Number of sides on the dice',
required: true,
choices: [
{ name: '6-sided', value: 6 },
{ name: '12-sided', value: 12 },
{ name: '20-sided', value: 20 },
],
})
build() {}
}Channel type filtering
The @SlashOption.Channel decorator accepts a channelTypes array that restricts which channel types the user can select. The values correspond to Discord ChannelType enum members. When the array is provided, the Discord client only shows channels matching the specified types in the option picker.
import { SlashCommand, SlashOption } from '@spraxium/common';
import { ChannelType } from 'discord.js';
@SlashCommand({
name: 'setup',
description: 'Configure the bot for this server.',
})
export class SetupCommand {
@SlashOption.Channel('log-channel', {
description: 'Channel for bot logs',
required: true,
channelTypes: [ChannelType.GuildText, ChannelType.GuildAnnouncement],
})
@SlashOption.Channel('voice-channel', {
description: 'Default voice channel',
required: false,
channelTypes: [ChannelType.GuildVoice, ChannelType.GuildStageVoice],
})
build() {}
}| ChannelType | Numeric value | Description |
|---|---|---|
| GuildText | 0 | Standard text channel in a guild. |
| GuildVoice | 2 | Voice channel in a guild. |
| GuildCategory | 4 | Category that contains other channels. |
| GuildAnnouncement | 5 | Announcement (news) channel in a guild. |
| AnnouncementThread | 10 | Thread inside an announcement channel. |
| PublicThread | 11 | Public thread open to all channel members. |
| PrivateThread | 12 | Private thread visible only to invited members. |
| GuildStageVoice | 13 | Stage channel for moderated audio events. |
| GuildForum | 15 | Forum channel where each post creates a thread. |
| GuildMedia | 16 | Media channel for rich-media posts. |
Localization with i18n keys
Both the @SlashCommand decorator and every @SlashOption decorator accept an optional i18n property of type SlashI18nKeys. This object contains two string keys, name and description, which the i18n module resolves to locale-specific strings during command registration. The resolved localizations are sent to Discord as part of the command payload, so users see command and option names in their Discord client language.
import { SlashCommand, SlashOption } from '@spraxium/common';
@SlashCommand({
name: 'locale',
description: 'Change your preferred language.',
i18n: {
name: 'commands.locale.name',
description: 'commands.locale.description',
},
})
export class LocaleCommand {
@SlashOption.String('language', {
description: 'The language to use.',
required: true,
i18n: {
name: 'commands.locale.options.language.name',
description: 'commands.locale.options.language.description',
},
choices: [
{ name: 'English (US)', value: 'en-US' },
{ name: 'Português (Brasil)', value: 'pt-BR' },
],
})
build() {}
}The i18n keys follow the same resolution pattern as the rest of the framework i18n system. They map to keys in your locale JSON files and support all Discord-supported locales. When a user with a Brazilian Portuguese Discord client opens the command picker, they see the translated command and option names automatically.
Design checklist for command definitions
Keep definitions free of business logic
The command class should contain only metadata decorators and an empty build() method (or named subcommand methods). Never import services, repositories, or runtime dependencies into a command definition file. This ensures the class can be imported by handlers, autocomplete handlers, and tests without creating dependency chains.
Use the most specific option type available
Prefer @SlashOption.User over @SlashOption.String for user IDs, @SlashOption.Channel over @SlashOption.String for channel references, and @SlashOption.Integer over @SlashOption.Number for whole numbers. Specific types give Discord better UI affordances like user pickers and channel dropdowns, and the handler receives fully resolved discord.js objects instead of raw strings.
Restrict channels with channelTypes
When a command expects a specific kind of channel, always provide the channelTypes array. This prevents users from selecting voice channels where you expect text channels, and the Discord client enforces the constraint before the interaction reaches your bot.
Prefer choices over autocomplete for small sets
Static choices arrays are displayed as a dropdown and require no handler code. Use them when the set of valid values is small and fixed (under 25 entries). Use autocomplete when the values are dynamic, come from a database, or exceed the 25-choice limit.
Set permissions at the definition level
Use defaultMemberPermissions to set the minimum permissions required to see the command in the picker. This is a UI hint that Discord enforces on the client side. Combine it with @UseGuards on the handler for server-side enforcement that cannot be bypassed.