Commands
Create and manage slash commands
Slash commands let users interact with your bot through Discord's command interface. Robo.js routes commands from the file system and handles registration automatically.
This guide covers the essentials. For the complete reference including all config fields, min/max constraints, channel type restrictions, option localizations, and the built-in /help command, see the @robojs/discordjs Commands reference.
Discord slash command picker showing a list of registered slash commands including /ping, /greet, and /color, with command descriptions visible next to each command name
Creating Commands
Create a file in src/commands named after the command. The file's default export is the handler function.
export default () => {
return 'Pong!'
}export default () => {
return 'Pong!'
}Returning a value uses Sage mode (Robo.js automatically sends the return value as the reply). You can also use the interaction object directly.
import type { ChatInputCommandInteraction } from 'discord.js'
export default (interaction: ChatInputCommandInteraction) => {
interaction.reply('Pong!')
}export default (interaction) => {
interaction.reply('Pong!')
}Both approaches register a /ping command when the file is named ping.ts.
Command Configuration
Export a config object using createCommandConfig() to set metadata like descriptions, options, and permissions.
import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Responds with Pong!'
})import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Responds with Pong!'
})| Field | Type | Description |
|---|---|---|
description | string | Command description shown in Discord |
contexts | CommandContext[] | Where the command is available |
defaultMemberPermissions | PermissionResolvable | Required permissions |
integrationTypes | CommandIntegrationType[] | Install types that can use this command |
nsfw | boolean | Restrict to age-gated channels |
options | CommandOption[] | Command parameters |
sage | false | SageOptions | Sage mode configuration |
timeout | number | Maximum execution time in milliseconds |
Options
Define parameters with the options array in your config. Access parsed values through the second parameter using CommandOptions<typeof config>.
import { createCommandConfig } from '@robojs/discordjs'
import type { ChatInputCommandInteraction, CommandOptions } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Greet someone',
options: [
{
name: 'user',
type: 'user',
description: 'User to greet',
required: true
},
{
name: 'message',
type: 'string',
description: 'Custom greeting'
}
]
} as const)
export default (interaction: ChatInputCommandInteraction, options: CommandOptions<typeof config>) => {
return `Hello ${options.user.username}! ${options.message ?? 'Welcome!'}`
}import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Greet someone',
options: [
{
name: 'user',
type: 'user',
description: 'User to greet',
required: true
},
{
name: 'message',
type: 'string',
description: 'Custom greeting'
}
]
})
export default (interaction, options) => {
return `Hello ${options.user.username}! ${options.message ?? 'Welcome!'}`
}The as const assertion tells TypeScript to infer exact option names and types from your config, so CommandOptions can provide correct autocomplete and type checking.
Discord showing the /greet slash command being used with a user option autocomplete dropdown visible, listing server members to select from, and a message option field below
Option Types
| Type | TypeScript Type |
|---|---|
string | string |
integer | number |
number | number |
boolean | boolean |
user | User |
channel | GuildBasedChannel |
member | GuildMember | null |
role | Role |
attachment | Attachment |
mention | GuildMember | Role |
Choices
Restrict an option to a fixed set of values with the choices array.
import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Chooses a color',
options: [
{
name: 'color',
description: 'Your favorite color',
type: 'string',
choices: [
{ name: 'Red', value: 'red' },
{ name: 'Green', value: 'green' },
{ name: 'Blue', value: 'blue' }
]
}
]
} as const)import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Chooses a color',
options: [
{
name: 'color',
description: 'Your favorite color',
type: 'string',
choices: [
{ name: 'Red', value: 'red' },
{ name: 'Green', value: 'green' },
{ name: 'Blue', value: 'blue' }
]
}
]
})Each choice has a name displayed to the user and a value passed to your handler.
Discord showing the /color slash command with a dropdown choice picker displaying Red, Green, and Blue as fixed options
Autocomplete
For dynamic suggestions, set autocomplete: true on the option and export an autocomplete function.
import type { AutocompleteInteraction } from 'discord.js'
const colors = ['red', 'green', 'blue', 'yellow', 'purple']
export const autocomplete = (interaction: AutocompleteInteraction) => {
const focused = interaction.options.getFocused()
const filtered = colors.filter((c) => c.startsWith(focused))
return filtered.map((c) => ({ name: c, value: c }))
}const colors = ['red', 'green', 'blue', 'yellow', 'purple']
export const autocomplete = (interaction) => {
const focused = interaction.options.getFocused()
const filtered = colors.filter((c) => c.startsWith(focused))
return filtered.map((c) => ({ name: c, value: c }))
}The function receives an AutocompleteInteraction and returns matching choices as the user types.
Subcommands
Nest files in folders to create subcommands. The folder name becomes the parent command and each file becomes a subcommand.
This creates /channel lock and /channel unlock.
Discord slash command picker showing the /channel command expanded into subcommands, with lock and unlock listed as subcommand options beneath the parent command
Subcommand Groups
Add another folder level for subcommand groups, creating a three-level hierarchy.
This creates /bot status idle.
Contexts and Integration Types
Control where your command is available with contexts and who can use it with integrationTypes. Use these when you want commands to work in DMs or with user-installed bots (installed by individual users rather than server admins).
import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Available everywhere',
contexts: ['Guild', 'BotDM', 'PrivateChannel'],
integrationTypes: ['GuildInstall', 'UserInstall']
})import { createCommandConfig } from '@robojs/discordjs'
export const config = createCommandConfig({
description: 'Available everywhere',
contexts: ['Guild', 'BotDM', 'PrivateChannel'],
integrationTypes: ['GuildInstall', 'UserInstall']
})| Context | Description |
|---|---|
Guild | Server channels |
BotDM | Direct messages with the bot |
PrivateChannel | Group DMs and other private channels |
| Integration Type | Description |
|---|---|
GuildInstall | Available in servers where the bot is installed |
UserInstall | Available to users who installed the bot, in any server |
Permissions
Use defaultMemberPermissions with Discord.js PermissionFlagsBits to restrict command access by server role.
import { createCommandConfig } from '@robojs/discordjs'
import { PermissionFlagsBits } from 'discord.js'
export const config = createCommandConfig({
description: 'Kick a member',
defaultMemberPermissions: PermissionFlagsBits.KickMembers
})import { createCommandConfig } from '@robojs/discordjs'
import { PermissionFlagsBits } from 'discord.js'
export const config = createCommandConfig({
description: 'Kick a member',
defaultMemberPermissions: PermissionFlagsBits.KickMembers
})Server admins can override default permissions for their own server. Default permissions may not apply as expected to subcommands due to a Discord limitation.
Command Registration
Robo.js registers commands automatically when you run robo dev or robo build. It uses hash caching to detect changes and only re-registers when needed.
To force re-registration, use the --force flag.
npx robo build --forceThis also cleans up commands that no longer exist in your src/commands directory.
Testing Commands
Test your commands programmatically with Mock Server, which simulates Discord's API locally and lets you verify command behavior without a live bot connection.
