Skip to main content
Version: v2.10.1

Before you continueโ€‹

Assuming you've followed the guide so far, your project directory should look something like this:

discord-bot/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ config.js
โ”œโ”€โ”€ index.js
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ package.json

Following this guide, create a src/ folder and move index.js and config.js into it.

Your new project directory should look like this:

discord-bot/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ config.js
โ”‚ โ””โ”€โ”€ index.js
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ package.json
warning

Make sure to update the "main" field in your package.json from "index.js" to "src/index.js".

note

This page requires a valid main file. Select the tab below for the type of commands you want to create.


note

For fully functional commands, three pieces need to be in place:

  1. Individual command files with their definitions and functionality.
  2. A command handler that dynamically reads and executes those files.
  3. A deployment script to register slash commands with Discord.

These can be completed in any order. All three are required before slash commands are functional. For message commands, only the first two are required. This page covers step 1.

Individual command filesโ€‹

Create a commands/music/ folder inside src/. This is where your command files will live. You'll use SlashCommandBuilder to define each command.

Each command file exports two things:

  • data โ€” the command definition registered with Discord.
  • execute() โ€” the function that runs when the command is used.

A slash command must always respond to the interaction. Discord enforces this to ensure a good user experience โ€” failing to respond will cause Discord to show the command as failed.

Create src/commands/music/play.js:

const { LoadTypes, StateTypes } = require("magmastream");
const { SlashCommandBuilder } = require("discord.js");

module.exports = {
data: new SlashCommandBuilder()
.setName("play")
.setDescription("Play a song or playlist.")
.addStringOption((option) =>
option
.setName("query")
.setDescription("The search query for the song or playlist")
.setRequired(true)
),

async execute(client, interaction) {
// Check if the user is in a voice channel
if (!interaction.member.voice.channel) {
return await interaction
.reply("You need to be in a voice channel to use this command.")
.catch((error) => console.log(`[ERROR] Failed to reply: ${error.message}`));
}

// Get the current player
let player = client.manager.getPlayer(interaction.guild.id);

// Check if the user is in the same voice channel as the bot
if (player && interaction.member.voice.channel.id !== player.voiceChannelId) {
return await interaction
.reply("You need to be in the same voice channel as the bot.")
.catch((error) => console.log(`[ERROR] Failed to reply: ${error.message}`));
}

// Defer the reply
await interaction.deferReply().catch((error) => console.log(`[ERROR] Failed to defer: ${error.message}`));

// Search for the query
const query = interaction.options.getString("query");
let res = await client.manager.search(query, interaction.member.user);

// Handle empty or errored results
if (res.loadType === LoadTypes.Empty || res.loadType === LoadTypes.Error) {
return await interaction
.editReply("No results found.")
.catch((error) => console.log(`[ERROR] Failed to edit reply: ${error.message}`));
}

// Create the player if it doesn't exist
try {
player = client.manager.create({
guildId: interaction.guild.id,
voiceChannelId: interaction.member.voice.channel.id,
textChannelId: interaction.channel.id,
selfDeafen: true,
volume: 100,
});

if (player.state !== StateTypes.Connected) player.connect();
} catch (error) {
return await interaction
.editReply(`An error occurred while creating the player: ${error.message}`)
.catch((error) => console.log(`[ERROR] Failed to edit reply: ${error.message}`));
}

// Handle the search result
switch (res.loadType) {
case LoadTypes.Track:
case LoadTypes.Search: {
const track = res.tracks[0];
player.queue.add(track);

if (!player.playing && !player.paused && (await player.queue.totalSize()) === 1) {
await player.play();
}

await interaction
.editReply(`Added \`${track.author} - ${track.title}\` to the queue.`)
.catch((error) => console.log(`[ERROR] Failed to edit reply: ${error.message}`));
break;
}
case LoadTypes.Playlist: {
const tracks = res.playlist.tracks;
player.queue.add(tracks);

if (!player.playing && !player.paused && (await player.queue.totalSize()) === tracks.length) {
await player.play();
}

await interaction
.editReply(`Added playlist \`${res.playlist.name}\` with \`${tracks.length}\` songs to the queue.`)
.catch((error) => console.log(`[ERROR] Failed to edit reply: ${error.message}`));
break;
}
}
},
};

Your project directory should now look like this:

discord-bot/
โ”œโ”€โ”€ node_modules/
โ”œโ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ commands/
โ”‚ โ”‚ โ””โ”€โ”€ music/
โ”‚ โ”‚ โ””โ”€โ”€ play.js
โ”‚ โ”œโ”€โ”€ config.js
โ”‚ โ””โ”€โ”€ index.js
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ package.json

Next stepsโ€‹

Add more commands by creating new files inside src/commands/music/. Next, move on to command handling to load your files and respond to interactions.


Resulting codeโ€‹

You can review the complete example on the GitHub repository here .