Slash CommandsInteractionsdiscord.js6 min read

Configure Slash Commands

Register slash commands globally and per-guild, handle interactions, options, subcommands and autocomplete.


What are Slash Commands?

Slash commands are the modern way to interact with Discord bots. They appear when users type / and offer autocomplete, type validation and better UX.

Step 1: Register commands

Create a deploy-commands.js file:

javascript
const { REST, Routes, SlashCommandBuilder } = require('discord.js');
require('dotenv').config();

const commands = [
  new SlashCommandBuilder()
    .setName('ping')
    .setDescription('Replies with pong and latency'),
  new SlashCommandBuilder()
    .setName('info')
    .setDescription('Shows user info')
    .addUserOption(option =>
      option.setName('user')
        .setDescription('The user to look up')
        .setRequired(true)
    ),
  new SlashCommandBuilder()
    .setName('config')
    .setDescription('Server configuration')
    .addSubcommand(sub =>
      sub.setName('prefix')
        .setDescription('Change the prefix')
        .addStringOption(opt =>
          opt.setName('new').setDescription('New prefix').setRequired(true)
        )
    )
];

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

(async () => {
  // Global registration (takes ~1 hour to propagate)
  await rest.put(
    Routes.applicationCommands(process.env.CLIENT_ID),
    { body: commands.map(c => c.toJSON()) }
  );
  console.log('Commands registered globally');

  // Per-guild registration (instant, ideal for development)
  await rest.put(
    Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.GUILD_ID),
    { body: commands.map(c => c.toJSON()) }
  );
  console.log('Commands registered in dev guild');
})();

Step 2: Handle interactions

javascript
client.on('interactionCreate', async interaction => {
  if (!interaction.isChatInputCommand()) return;

  const { commandName } = interaction;

  if (commandName === 'ping') {
    const latency = Date.now() - interaction.createdTimestamp;
    await interaction.reply(`Pong! Latency: ${latency}ms`);
  }

  if (commandName === 'info') {
    const user = interaction.options.getUser('user');
    await interaction.reply(`User: ${user.tag}\nID: ${user.id}`);
  }
});

Step 3: Advanced options

Available option types:

javascript
// String with predefined choices
.addStringOption(opt =>
  opt.setName('color')
    .setDescription('Pick a color')
    .addChoices(
      { name: 'Red', value: 'red' },
      { name: 'Blue', value: 'blue' }
    )
)

// Integer with range
.addIntegerOption(opt =>
  opt.setName('amount')
    .setDescription('Amount')
    .setMinValue(1)
    .setMaxValue(100)
)

Step 4: Autocomplete

javascript
// In the builder
.addStringOption(opt =>
  opt.setName('game')
    .setDescription('Game name')
    .setAutocomplete(true)
)

// In the handler
client.on('interactionCreate', async interaction => {
  if (interaction.isAutocomplete()) {
    const focused = interaction.options.getFocused();
    const choices = ['Minecraft', 'FiveM', 'CS2', 'Valorant'];
    const filtered = choices.filter(c =>
      c.toLowerCase().startsWith(focused.toLowerCase())
    );
    await interaction.respond(
      filtered.map(c => ({ name: c, value: c }))
    );
  }
});

Recommendations

  • Use per-guild registration during development (it's instant)
  • Register globally only when the command is production-ready
  • Implement error handling in every command
  • Use interaction.deferReply() if the command takes more than 3 seconds

Was this guide helpful?