Setting Up Discord Slash Commands πŸ”ͺ

16/03/2025 Development 5 mins read
Table Of Contents

Prerequisites

  • An existing Discord bot project using Discord.js v14+
  • Node.js v16.9.0 or higher
  • Basic understanding of Discord.js

Understanding Slash Commands

Slash commands allow users to interact with your bot using /command syntax directly in Discord’s interface. They provide:

  • Auto-completion of command names and options
  • Standardized command structure
  • Better permission handling
  • Improved user experience

Project Setup

First, ensure you have the required dependencies:

Terminal window
npm install discord.js @discordjs/builders @discordjs/rest discord-api-types
Terminal window
yarn add discord.js @discordjs/builders @discordjs/rest discord-api-types

Creating Command Files

Create a commands folder in your project and add individual command files:

my-discord-bot/
β”œβ”€β”€ commands/
β”‚ β”œβ”€β”€ ping.js
β”‚ β”œβ”€β”€ info.js
β”‚ └── help.js
β”œβ”€β”€ index.js
└── deploy-commands.js

Example Command File

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!');
},
};

Creating a Command with Options

commands/echo.js
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('echo')
.setDescription('Echoes your input')
.addStringOption(option =>
option.setName('message')
.setDescription('The message to echo')
.setRequired(true))
.addBooleanOption(option =>
option.setName('ephemeral')
.setDescription('Whether the echo should be ephemeral')),
async execute(interaction) {
const message = interaction.options.getString('message');
const ephemeral = interaction.options.getBoolean('ephemeral') ?? false;
await interaction.reply({
content: message,
ephemeral: ephemeral
});
},
};

Deploying Commands

Create a deployment script to register your commands with Discord:

deploy-commands.js
const { REST, Routes } = require('discord.js');
const fs = require('node:fs');
const path = require('node:path');
require('dotenv').config();
const commands = [];
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
commands.push(command.data.toJSON());
}
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
(async () => {
try {
console.log(`Started refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands
const data = await rest.put(
Routes.applicationCommands(process.env.CLIENT_ID),
{ body: commands },
);
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
} catch (error) {
console.error(error);
}
})();

Update your .env file to include:

# .env
DISCORD_TOKEN=your_token_here
CLIENT_ID=your_application_id_here

Run the deployment script:

Terminal window
node deploy-commands.js

Handling Commands in Your Main Bot File

Update your main bot file to handle the slash commands:

index.js
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits } = require('discord.js');
require('dotenv').config();
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// Create a collection for commands
client.commands = new Collection();
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
if ('data' in command && 'execute' in command) {
client.commands.set(command.data.name, command);
} else {
console.log(`[WARNING] The command at ${filePath} is missing required "data" or "execute" property.`);
}
}
// When the client is ready, run this code
client.once(Events.ClientReady, () => {
console.log('Ready!');
});
// Listen for interactions (slash commands)
client.on(Events.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);
await interaction.reply({
content: 'There was an error executing this command!',
ephemeral: true
});
}
});
client.login(process.env.DISCORD_TOKEN);

Advanced Command Options

Discord.js supports various command option types:

  • String: .addStringOption()
  • Integer: .addIntegerOption()
  • Boolean: .addBooleanOption()
  • User: .addUserOption()
  • Channel: .addChannelOption()
  • Role: .addRoleOption()
  • Mentionable: .addMentionableOption()
  • Number: .addNumberOption()
  • Attachment: .addAttachmentOption()

Example with Multiple Option Types

commands/profile.js
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('profile')
.setDescription('Get profile information')
.addUserOption(option =>
option.setName('target')
.setDescription('The user to get info about')
.setRequired(false))
.addStringOption(option =>
option.setName('format')
.setDescription('Output format')
.setRequired(false)
.addChoices(
{ name: 'Simple', value: 'simple' },
{ name: 'Detailed', value: 'detailed' },
{ name: 'Full', value: 'full' },
)),
async execute(interaction) {
const target = interaction.options.getUser('target') ?? interaction.user;
const format = interaction.options.getString('format') ?? 'simple';
// Implementation details...
await interaction.reply(`Showing ${format} profile for ${target.username}`);
},
};

Subcommands

You can organize related commands using subcommands:

commands/settings.js
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('settings')
.setDescription('Manage bot settings')
.addSubcommand(subcommand =>
subcommand
.setName('view')
.setDescription('View current settings'))
.addSubcommand(subcommand =>
subcommand
.setName('prefix')
.setDescription('Change the bot prefix')
.addStringOption(option =>
option.setName('character')
.setDescription('The new prefix character')
.setRequired(true))),
async execute(interaction) {
if (interaction.options.getSubcommand() === 'view') {
await interaction.reply('Current settings: ...');
} else if (interaction.options.getSubcommand() === 'prefix') {
const newPrefix = interaction.options.getString('character');
await interaction.reply(`Prefix updated to: ${newPrefix}`);
}
},
};