---
title: Handling command interactions
category: Creating your bot
---

# Command handling

Unless your bot project is small, it's not a very good idea to have a single file with a giant _`if`_/_`else if`_ chain for commands. If you want to implement features into your bot and make your development process a lot less painful, you'll want to implement a command handler. Let's get started on that!

<Alert title="Read first!" type="info">
	For fully functional slash commands, there are three important pieces of code that need to be written. They are:

    1. The [individual command files](slash-commands), containing their definitions and functionality.
    2. The command handler, which dynamically reads the files and executes the commands.
    3. The [command deployment script](registering-slash-commands), to register your slash commands with Discord so they appear in the interface.

    These steps can be done in any order, but **all are required** before the commands are fully functional.

    This page details how to complete **Step 2**. Make sure to also complete the other pages linked above!

</Alert>

## Loading command files

Now that your command files have been created, your bot needs to load these files on startup.

In your _`index.js`_ file, make these additions to the base template:

<CH.Code>

```js JavaScript mark=1:4,9
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { Client, Collection, Events, GatewayIntentBits } from 'discord.js';
import config from './config.json' assert { type: 'json' };

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

const commands = new Collection();

client.once(Events.ClientReady, () => {
	console.log('Ready!');
});
```

```ts TypeScript mark=1:11,16:21
import { readdir } from 'node:fs/promises';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import {
	Client,
	Collection,
	Events,
	GatewayIntentBits,
	type RESTPostAPIChatInputApplicationCommandsJSONBody,
	type ChatInputCommandInteraction,
} from 'discord.js';
import config from './config.json';

const client = new Client({ intents: [GatewayIntentBits.Guilds] });

interface CommandModule {
	data: RESTPostAPIChatInputApplicationCommandsJSONBody;
	execute(interaction: ChatInputCommandInteraction): Promise<void>;
}

const commands = new Collection<string, CommandModule>();

client.once(Events.ClientReady, () => {
	console.log('Ready!');
});
```

</CH.Code>

<Alert title="Tip" type="info">
	- The [`fs`](https://nodejs.org/api/fs.html) module is Node's native file system module. _`readdir`_ is used to read
	the _`commands`_ directory and identify our command files. - The [`path`](https://nodejs.org/api/path.html) module is
	Node's native path utility module. _`join`_ helps construct paths to access files and directories. One of the
	advantages of _`path.join`_ is that it automatically detects the operating system and uses the appropriate joiners. -
	The [`url`](https://nodejs.org/api/url.html) module provides utilities for URL resolution and parsing.
	_`fileURLToPath`_ ensuring a cross-platform valid absolute path string.
	- The{' '}
	<DocsLink type="class" parent="Collection" /> class extends JavaScript's native
	[_`Map`_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) class, and includes
	more extensive, useful functionality. _`Collection`_ is used to store and efficiently retrieve commands for execution.

</Alert>

Next, using the modules imported above, dynamically retrieve your command files with a few more additions to the _`index.js`_ file:

<CH.Code>

```js JavaScript focus=3:15
const commands = new Collection();

const commandsPath = fileURLToPath(new URL('commands', import.meta.url));
const commandFiles = await readdir(commandsPath).then((files) => files.filter((file) => file.endsWith('.js')));

for (const file of commandFiles) {
	const filePath = join(commandsPath, file);
	const command = await import(filePath);
	// Set a new item in the Collection with the key as the command name and the value as the exported module
	if ('data' in command && 'execute' in command) {
		commands.set(command.data.name, command);
	} else {
		console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
	}
}
```

```ts TypeScript focus=3:15
const commands = new Collection<string, CommandModule>();

const commandsPath = fileURLToPath(new URL('commands', import.meta.url));
const commandFiles = await readdir(commandsPath).then((files) => files.filter((file) => file.endsWith('.js')));

for (const file of commandFiles) {
	const filePath = join(commandsPath, file);
	const command = await import(filePath);
	// Set a new item in the Collection with the key as the command name and the value as the exported module
	if ('data' in command && 'execute' in command) {
		commands.set(command.data.name, command);
	} else {
		console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
	}
}
```

</CH.Code>

First, [url.fileURLToPath()](https://nodejs.org/api/url.html) helps to construct a path to the _`commands`_ directory. The [fs.readdir()](https://nodejs.org/api/fs.html#fspromisesreaddirpath-options) method then reads the path to the directory and returns a Promise which resolves to an array of all the file names it contains, currently _`['ping.js', 'server.js', 'user.js']`_. To ensure only command files get processed, _`Array.filter()`_ removes any non-JavaScript files from the array.

With the correct files identified, the last step is to loop over the array and dynamically set each command into the _`commands`_ Collection. For each file being loaded, check that it has at least the _`data`_ and _`execute`_ properties. This helps to prevent errors resulting from loading empty, unfinished or otherwise incorrect command files while you're still developing.

## Receiving command interactions

Every slash command is an _`interaction`_, so to respond to a command, you need to create a listener for the <DocsLink type="class" parent="Client" symbol="e-interactionCreate" /> event that will execute code when your application receives an interaction. Place the code below in the _`index.js`_ file you created earlier.

<CH.Code>

```js
client.on(Events.InteractionCreate, (interaction) => {
	console.log(interaction);
});
```

</CH.Code>

Not every interaction is a slash command (e.g. _`MessageComponent`_ interactions). Make sure to only handle slash commands in this function by making use of the <DocsLink type="class" parent="BaseInteraction" symbol="isChatInputCommand" brackets /> method to exit the handler if another type is encountered. This method also provides type guarding for TypeScript users, narrowing the type from _`BaseInteraction`_ to <DocsLink type="class" parent="ChatInputCommandInteraction" />.

<CH.Code>

```js focus=2
client.on(Events.InteractionCreate, (interaction) => {
	if (!interaction.isChatInputCommand()) return;
	console.log(interaction);
});
```

</CH.Code>

## Executing commands

When your bot receives a <DocsLink type="class" parent="Client" symbol="e-interactionCreate" /> event, the interaction object contains all the information you need to dynamically retrieve and execute your commands!

Let's take a look at the _`ping`_ command again. Note the _`execute()`_ function that will reply to the interaction with "Pong!".

<CH.Code>

```js
export const data = {
	name: 'ping',
	description: 'Replies with Pong!',
};

export async function execute(interaction) {
	await interaction.reply('Pong!');
}
```

</CH.Code>

First, you need to get the matching command from the _`commands`_ Collection based on the _`interaction.commandName`_. If no matching command is found, log an error to the console and ignore the event.

With the right command identified, all that's left to do is call the command's _`.execute()`_ method and pass in the _`interaction`_ variable as its argument. Note that the event listener has been made _`async`_, allowing Promises to be awaited. In case something goes wrong and the Promise rejects, catch and log any error to the console.

<CH.Code>

```js focus=4:20
// focus[37:42]
client.on(Events.InteractionCreate, async (interaction) => {
	if (!interaction.isChatInputCommand()) return;

	const command = commands.get(interaction.commandName);

	if (!command) {
		console.error(`No command matching ${interaction.commandName} was found.`);
		return;
	}

	try {
		await command.execute(interaction);
	} catch (error) {
		console.error(error);
		if (interaction.replied || interaction.deferred) {
			await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
		} else {
			await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
		}
	}
});
```

</CH.Code>

## Command categories

So far, all of your command files are in a single _`commands`_ folder. This is fine at first, but as your project grows, the number of files in the _`commands`_ folder will too. Keeping track of that many files can be a little tough. To make this a little easier, you can categorize your commands and put them in subfolders inside the _`commands`_ folder. You will have to make a few changes to your existing code in _`index.js`_ for this to work out.

If you've been following along, your project structure should look something like this:

![Project structure before sorting](/assets/before-sorting.png)

After moving your commands into subfolders, it will look something like this:

![Project structure after sorting](/assets/after-sorting.png)

<Alert title="Warning" type="warning">
	Make sure you put every command file you have inside one of the new subfolders. Leaving a command file directly under
	the _`commands`_ folder will create problems.
</Alert>

It is not necessary to name your subfolders exactly like we have named them here. You can create any number of subfolders and name them whatever you want. Although, it is a good practice to name them according to the type of commands stored inside them.

Back in your _`index.js`_ file, where the code to [dynamically read command files](#loading-command-files) is, use the same pattern to read the subfolder directories, and then require each command inside them.

<CH.Code>

```js JavaScript focus=3:7,19
const commands = new Collection();

const foldersPath = fileURLToPath(new URL('commands', import.meta.url));
const commandFolders = await readdir(foldersPath);

for (const folder of commandFolders) {
	const commandsPath = join(foldersPath, folder);
	const commandFiles = await readdir(commandsPath).then((files) => files.filter((file) => file.endsWith('.js')));
	for (const file of commandFiles) {
		const filePath = join(commandsPath, file);
		const command = await import(filePath);
		// Set a new item in the Collection with the key as the command name and the value as the exported module
		if ('data' in command && 'execute' in command) {
			commands.set(command.data.name, command);
		} else {
			console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
		}
	}
}
```

```ts Typescript mark=3:7,19
const commands = new Collection<string, CommandModule>();

const foldersPath = fileURLToPath(new URL('commands', import.meta.url));
const commandFolders = await readdir(foldersPath);

for (const folder of commandFolders) {
	const commandsPath = join(foldersPath, folder);
	const commandFiles = await readdir(commandsPath).then((files) => files.filter((file) => file.endsWith('.js')));
	for (const file of commandFiles) {
		const filePath = join(commandsPath, file);
		const command = await import(filePath);
		// Set a new item in the Collection with the key as the command name and the value as the exported module
		if ('data' in command && 'execute' in command) {
			commands.set(command.data.name, command);
		} else {
			console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
		}
	}
}
```

</CH.Code>

That's it! When creating new files for commands, make sure you create them inside one of the subfolders (or a new one) in the _`commands`_ folder.

#### Next steps

Your command files are now loaded into your bot, and the event listener is prepared and ready to respond. In the next section, we cover the final step - a command deployment script you'll need to register your commands so they appear in the Discord client.

#### Resulting code

<ResultingCode />

It also includes some bonus commands!
