Discord Bot Commands with Slash Commands
Modern Discord bots use slash commands for a better user experience. This guide shows you how to implement them.
Why Slash Commands?
- Better UX: Native Discord interface with autocomplete
 - Permissions: Built-in permission handling
 - Discoverability: Users can see available commands
 - Validation: Type checking and required parameters
 
Setup
Install Dependencies
npm install discord.js @discordjs/rest discord-api-typesBot Configuration
Make sure your bot has these intents:
const { Client, GatewayIntentBits } = require('discord.js');
const client = new Client({  intents: [    GatewayIntentBits.Guilds,    GatewayIntentBits.GuildMessages,  ]});Creating Commands
Basic Command
Create commands/ping.js:
const { SlashCommandBuilder } = require('discord.js');
module.exports = {  data: new SlashCommandBuilder()    .setName('ping')    .setDescription('Replies with Pong!'),
  async execute(interaction) {    await interaction.reply('Pong!');  },};Command with Options
Create commands/user.js:
const { SlashCommandBuilder } = require('discord.js');
module.exports = {  data: new SlashCommandBuilder()    .setName('user')    .setDescription('Get info about a user')    .addUserOption(option =>      option        .setName('target')        .setDescription('The user to get info about')        .setRequired(true)    ),
  async execute(interaction) {    const user = interaction.options.getUser('target');    await interaction.reply(`User: ${user.tag}\nID: ${user.id}`);  },};Command with Multiple Options
const { SlashCommandBuilder } = require('discord.js');
module.exports = {  data: new SlashCommandBuilder()    .setName('poll')    .setDescription('Create a poll')    .addStringOption(option =>      option        .setName('question')        .setDescription('The poll question')        .setRequired(true)    )    .addStringOption(option =>      option        .setName('option1')        .setDescription('First option')        .setRequired(true)    )    .addStringOption(option =>      option        .setName('option2')        .setDescription('Second option')        .setRequired(true)    ),
  async execute(interaction) {    const question = interaction.options.getString('question');    const option1 = interaction.options.getString('option1');    const option2 = interaction.options.getString('option2');
    const poll = `📊 **${question}**\n\n1️⃣ ${option1}\n2️⃣ ${option2}`;
    const message = await interaction.reply({      content: poll,      fetchReply: true    });
    await message.react('1️⃣');    await message.react('2️⃣');  },};Registering Commands
Create deploy-commands.js:
const { REST, Routes } = require('discord.js');const fs = require('fs');require('dotenv').config();
const commands = [];const commandFiles = fs.readdirSync('./commands')  .filter(file => file.endsWith('.js'));
for (const file of commandFiles) {  const command = require(`./commands/${file}`);  commands.push(command.data.toJSON());}
const rest = new REST({ version: '10' })  .setToken(process.env.DISCORD_TOKEN);
(async () => {  try {    console.log(`Registering ${commands.length} commands...`);
    // Guild commands (instant update)    await rest.put(      Routes.applicationGuildCommands(        process.env.CLIENT_ID,        process.env.GUILD_ID      ),      { body: commands }    );
    // OR Global commands (takes up to 1 hour)    // await rest.put(    //   Routes.applicationCommands(process.env.CLIENT_ID),    //   { body: commands }    // );
    console.log('Successfully registered commands!');  } catch (error) {    console.error(error);  }})();Run it:
node deploy-commands.jsHandling Commands
In your main index.js:
const fs = require('fs');const { Client, Collection, GatewayIntentBits } = require('discord.js');
const client = new Client({  intents: [GatewayIntentBits.Guilds]});
// Load commandsclient.commands = new Collection();const commandFiles = fs.readdirSync('./commands')  .filter(file => file.endsWith('.js'));
for (const file of commandFiles) {  const command = require(`./commands/${file}`);  client.commands.set(command.data.name, command);}
// Handle interactionsclient.on('interactionCreate', async interaction => {  if (!interaction.isChatInputCommand()) return;
  const command = client.commands.get(interaction.commandName);
  if (!command) return;
  try {    await command.execute(interaction);  } catch (error) {    console.error(error);
    if (interaction.replied || interaction.deferred) {      await interaction.followUp({        content: 'Error executing command!',        ephemeral: true      });    } else {      await interaction.reply({        content: 'Error executing command!',        ephemeral: true      });    }  }});
client.login(process.env.DISCORD_TOKEN);Advanced Features
Subcommands
const { SlashCommandBuilder } = require('discord.js');
module.exports = {  data: new SlashCommandBuilder()    .setName('config')    .setDescription('Bot configuration')    .addSubcommand(subcommand =>      subcommand        .setName('prefix')        .setDescription('Set the bot prefix')        .addStringOption(option =>          option            .setName('value')            .setDescription('New prefix')            .setRequired(true)        )    )    .addSubcommand(subcommand =>      subcommand        .setName('channel')        .setDescription('Set the log channel')        .addChannelOption(option =>          option            .setName('target')            .setDescription('The channel')            .setRequired(true)        )    ),
  async execute(interaction) {    const subcommand = interaction.options.getSubcommand();
    if (subcommand === 'prefix') {      const prefix = interaction.options.getString('value');      await interaction.reply(`Prefix set to: ${prefix}`);    } else if (subcommand === 'channel') {      const channel = interaction.options.getChannel('target');      await interaction.reply(`Log channel set to: ${channel}`);    }  },};Choices (Dropdown)
.addStringOption(option =>  option    .setName('language')    .setDescription('Choose a language')    .setRequired(true)    .addChoices(      { name: 'JavaScript', value: 'js' },      { name: 'Python', value: 'py' },      { name: 'TypeScript', value: 'ts' }    ))Permissions
const { SlashCommandBuilder, PermissionFlagsBits } = require('discord.js');
module.exports = {  data: new SlashCommandBuilder()    .setName('ban')    .setDescription('Ban a user')    .setDefaultMemberPermissions(PermissionFlagsBits.BanMembers)    .addUserOption(option =>      option        .setName('target')        .setDescription('User to ban')        .setRequired(true)    ),
  async execute(interaction) {    // Only users with Ban Members permission can use this    const user = interaction.options.getUser('target');    await interaction.guild.members.ban(user);    await interaction.reply(`Banned ${user.tag}`);  },};Best Practices
- Use ephemeral replies for errors and private info
 - Defer replies for long-running operations
 - Validate permissions in both command definition and execution
 - Handle errors gracefully with try-catch
 - Use guild commands for testing (instant updates)
 - Document your commands with clear descriptions
 
Troubleshooting
Commands not showing up?
- Wait up to 1 hour for global commands
 - Use guild commands for instant testing
 - Check bot has 
applications.commandsscope 
“Unknown interaction” error?
- Reply within 3 seconds or defer first
 - Make sure event handler is set up correctly
 - Check command is registered properly
 
Permission errors?
- Verify bot has required permissions in server
 - Check role hierarchy for moderation commands
 - Ensure 
applications.commandsscope is enabled