feat(guide): port legacy guide (#10938)

* feat: initial attempt at porting legacy guide

* feat: completion of legacy guide backport

* chore: lockfile shenanigans

* fix: handle svgs

* fix: replace svg with mermaid integration

* chore: format

* chore: remove unnecssary bullet

* chore: cleanup code highlights

* chore: explicit return

* chore: move display components after interactive components in sidebar

* chore: voice

* top link should be installation
* add docs link to sidebar

* feat: subguide-based accent styles

* chore: don't list faq twice

* chore: mention display components in interactive components

* fix: remove unoccs/order rule from guide

* chore: redirect to legacy guide instead of /guide root

* refactor: use `<kbd>`

* refactor: more kbd use

* Update apps/guide/content/docs/legacy/app-creation/handling-events.mdx

Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com>

* chore: fix typos

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: fix typos

* chore: fix links regarding secret stores across coding platforms

* chore: fix typo

* chore: link node method directly

Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>

* chore: typos

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* chore: typo

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* fix: prevent v14 changes from being listed twice

* chore: prefer relative links

* chore: missed link conversion

* chore: missed link conversion

* chore: fix link

* chore: remove legacy code highlight markers

* chore: rephrase and extend contributing guidelines

* feat(setup): suggest cli flag over dotenv package

* chore: move introduction in sidebar

better navigation experience if the 'next page' in intro refers to getting started vs. updating/faq

* fix: replace outdated link

* fix: update voice dependencies

* chore: update node install instructions

* fix: list in missing access callout

* chore: match bun env file format

* chore: restore ffmpeg disclaimer

* fix: lockfile conflict

* chore: action row typo

Co-authored-by: Vlad Frangu <me@vladfrangu.dev>

* chore: no longer use at-next for pino

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
Co-authored-by: Qjuh <76154676+Qjuh@users.noreply.github.com>
Co-authored-by: Naiyar <137700126+imnaiyar@users.noreply.github.com>
Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
This commit is contained in:
Souji
2025-07-08 15:01:50 +02:00
committed by GitHub
parent ee3ca6f7c6
commit bc6005f446
136 changed files with 11847 additions and 48 deletions

View File

@@ -0,0 +1,141 @@
---
title: Creating slash commands
---
## Creating slash commands
import { Step, Steps } from 'fumadocs-ui/components/steps';
import { File, Folder, Files } from 'fumadocs-ui/components/files';
Discord allows developers to register [slash commands](https://discord.com/developers/docs/interactions/application-commands), which provide users a first-class way of interacting directly with your application.
Slash commands provide a huge number of benefits over manual message parsing, including:
- Integration with the Discord client interface.
- Automatic command detection and parsing of the associated options/arguments.
- Typed argument inputs for command options, e.g. "String", "User", or "Role".
- Validated or dynamic choices for command options.
- In-channel private responses (ephemeral messages).
- Pop-up form-style inputs for capturing additional information.
...and many more!
## Before you continue
Assuming you've followed the guide so far, your project directory should look something like this:
<Files>
<Folder name="discord-bot" defaultOpen>
<Folder name="node_modules" defaultOpen />
<File name="config.json" />
<File name="index.js" />
<File name="package-lock.json" />
<File name="package.json" />
</Folder>
</Files>
<Steps>
<Step>
### Command Files
The individual command files, containing slash command definitions and functionality.
</Step>
<Step>
### Command Handler
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
</Step>
<Step>
### Command Deployment
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
</Step>
</Steps>
These steps can be followed in any order, but are all required to make your bot work. This page details step **1**. Make sure you also check out the other linked pages.
## Individual command files
Create a new folder named `commands` and a subfolder named `utility` inside it, which is where you'll store all of your utility command files. You'll be using the class to construct the command definitions.
At a minimum, the definition of a slash command must have a name and a description. Slash command names must be between 1-32 characters and contain no capital letters, spaces, or symbols other than `-` and `_`. Using the builder, a simple `ping` command definition would look like this:
```js
new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!');
```
A slash command also requires a function to run when the command is used, to respond to the interaction. Using an interaction response method confirms to Discord that your bot successfully received the interaction, and has responded to the user. Discord enforces this to ensure that all slash commands provide a good user experience (UX). Failing to respond will cause Discord to show that the command failed, even if your bot is performing other actions as a result.
The simplest way to acknowledge and respond to an interaction is the `interaction.reply()` method. Other methods of replying are covered on the [Response methods](../slash-commands/response-methods) page later in this section.
```js
async execute(interaction) {
await interaction.reply('Pong!')
}
```
Put these two together by creating a `ping.js` file in the `commands/utility` folder for your first command. Inside this file, you're going to define and export two items.
- The `data` property, which will provide the command definition shown above for registering to Discord.
- The `execute` method, which will contain the functionality to run from our event handler when the command is used.
These are placed inside `module.exports` so they can be read by other files; namely the command loader and command deployment scripts mentioned earlier.
```js title="commands/utility/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!');
},
};
```
<Callout>
[`module.exports`](https://nodejs.org/api/modules.html#modules_module_exports) is how you export data in Node.js so that you can [`require()`](https://nodejs.org/api/modules.html#modules_require_id) it in other files.
If you need to access your client instance from inside a command file, you can access it via `interaction.client`. If you need to access external files, packages, etc., you should `require()` them at the top of the file.
</Callout>
That's it for your basic ping command. Below are examples of two more commands we're going to build upon throughout the guide, so create two more files for these before you continue reading.
```js tab="User" title="commands/utility/user.js"
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder().setName('user').setDescription('Provides information about the user.'),
async execute(interaction) {
// interaction.user is the object representing the User who ran the command
// interaction.member is the GuildMember object, which represents the user in the specific guild
await interaction.reply(
`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`,
);
},
};
```
```js tab="Server" title="commands/utility/server.js"
const { SlashCommandBuilder } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder().setName('server').setDescription('Provides information about the server.'),
async execute(interaction) {
// interaction.guild is the object representing the Guild in which the command was run
await interaction.reply(
`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`,
);
},
};
```
#### Next steps
You can implement additional commands by creating new files within a dedicated subfolder in the `commands` folder, but these three are the ones we're going to use for the examples as we go on. For now let's move on to the code you'll need for command handling, to load the files and respond to incoming interactions.

View File

@@ -0,0 +1,153 @@
---
title: Registering Commands
---
import { Step, Steps } from 'fumadocs-ui/components/steps';
For fully functional slash commands, you need three important pieces of code:
<Steps>
<Step>
### Command Files
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
</Step>
<Step>
### Command Handler
The [command handler](./handling-commands), dynamically reads the command files and executes commands.
</Step>
<Step>
### Command Deployment
The command deployment script to register your slash commands with Discord.
</Step>
</Steps>
These steps can be followed in any order, but are all required to make your bot work. This page details step **3**. Make sure you also check out the other linked pages.
## Command registration
Slash commands can be registered in two ways; in one specific guild, or for every guild the bot is in. We're going to look at single-guild registration first, as this is a good way to develop and test your commands before a global deployment.
Your application will need the `applications.commands` scope authorized in a guild for any of its slash commands to appear, and to be able to register them in a specific guild without error.
Slash commands only need to be registered once, and updated when the definition (description, options etc) is changed. As there is a daily limit on command creations, it's not necessary nor desirable to connect a whole client to the gateway or do this on every `ready` event. As such, a standalone script using the lighter REST manager is preferred.
This script is intended to be run separately, only when you need to make changes to your slash command **definitions** - you're free to modify parts such as the execute function as much as you like without redeployment.
### Guild commands
Create a `deploy-commands.js` file in your project directory. This file will be used to register and update the slash commands for your bot application.
Add two more properties to your `config.json` file, which we'll need in the deployment script:
- `clientId`: Your application's client id ([Discord Developer Portal](https://discord.com/developers/applications) > "General Information" > application id)
- `guildId`: Your development server's id ([Enable developer mode](https://support.discord.com/hc/en-us/articles/206346498) > Right-click the server title > "Copy ID")
```json title="config.json"
{
"token": "your-token-goes-here",
// [!code ++:2]
"clientId": "your-application-id-goes-here",
"guildId": "your-server-id-goes-here"
}
```
With these defined, you can use the deployment script below:
```js title="deploy-commands.js" lineNumbers
const { REST, Routes } = require('discord.js');
const { clientId, guildId, token } = require('./config.json');
const fs = require('node:fs');
const path = require('node:path');
const commands = [];
// Grab all the command folders from the commands directory you created earlier
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
// Grab all the command files from the commands directory you created earlier
const commandsPath = path.join(foldersPath, folder);
const commandFiles = fs.readdirSync(commandsPath).filter((file) => file.endsWith('.js'));
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
if ('data' in command && 'execute' in command) {
commands.push(command.data.toJSON());
} else {
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
}
}
}
// Construct and prepare an instance of the REST module
const rest = new REST().setToken(token);
// and deploy your commands!
(async () => {
try {
console.log(`Started refreshing ${commands.length} application (/) commands.`);
// [!code word:Guild]
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands });
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
} catch (error) {
// And of course, make sure you catch and log any errors!
console.error(error);
}
})();
```
Once you fill in these values, run `node deploy-commands.js` in your project directory to register your commands to the guild specified. If you see the success message, check for the commands in the server by typing `/`! If all goes well, you should be able to run them and see your bot's response in Discord!
### Global commands
Global application commands will be available in all the guilds your application has the `applications.commands` scope authorized in, and in direct messages by default.
To deploy global commands, you can use the same script from the guild commands section above and simply adjust the route in the script to `.applicationCommands(clientId)`
```js
await rest.put(Routes.applicationCommands(clientId), { body: commands });
```
### Where to deploy
<Callout>
Guild-based deployment of commands is best suited for development and testing in your own personal server. Once you're satisfied that it's ready, deploy the command globally to publish it to all guilds that your bot is in.
You may wish to have a separate application and token in the Discord Dev Portal for your dev application, to avoid duplication between your guild-based commands and the global deployment.
</Callout>
#### Further reading
You've successfully sent a response to a slash command! However, this is only the most basic of command event and response functionality. Much more is available to enhance the user experience including:
<Cards>
<Card title="Event Handling" href="./handling-events">
Apply a similar dynamic, modular handling approach to client events.
</Card>
<Card title="Response Methods" href="../slash-commands/response-methods">
Utilize different response methods for slash commands.
</Card>
<Card title="Advanced Command Creation" href="../slash-commands/advanced-creation">
Expand on the command examples with additional, validated, option types.
</Card>
<Card title="Display Components" href="../popular-topics/display-components">
Level up command responses with formatted content and display components.
</Card>
<Card title="Interactive Components" href="../interactive-components/action-rows">
Enhance your commands with more input methods using Buttons, Select Menus, and Modals!
</Card>
</Cards>

View File

@@ -0,0 +1,171 @@
---
title: Command Handling
---
import { Step, Steps } from 'fumadocs-ui/components/steps';
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!
For fully functional slash commands, you need three important pieces of code:
<Steps>
<Step>
### Command Files
The individual command files, containing [slash command](./creating-commands) definitions and functionality.
</Step>
<Step>
### Command Handler
The command handler, dynamically reads the command files and executes commands.
</Step>
<Step>
### Command Deployment
The command [deployment script](./deploying-commands) to register your slash commands with Discord.
</Step>
</Steps>
These steps can be followed in any order, but are all required to make your bot work. This page details step **2**. Make sure you also check out the other linked pages.
## 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:
```js title="index.js" lineNumbers
// [!code ++:4]
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, Events, GatewayIntentBits, MessageFlags } = require('discord.js');
const { token } = require('./config.json');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
client.commands = new Collection(); // [!code ++]
```
We recommend attaching a `.commands` property to your client instance so that you can access your commands in other files. The rest of the examples in this guide will follow this convention. For TypeScript users, we recommend extending the base Client class to add this property, [casting](https://www.typescripttutorial.net/typescript-tutorial/type-casting/), or [augmenting the module type](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation).
<Callout>
- The [`fs`](https://nodejs.org/api/fs.html) module is Node's native file system module. `fs` 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. `path` helps construct paths to access files and directories. One of the advantages of the
`path` module is that it automatically detects the operating system and uses the appropriate joiners. - The
`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.
</Callout>
Next, using the modules imported above, dynamically retrieve your command files with a few more additions to the `index.js` file:
```js title="index.js" lineNumbers=12
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
const commandsPath = path.join(foldersPath, folder);
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);
// 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) {
client.commands.set(command.data.name, command);
} else {
console.log(`[WARNING] The command at ${filePath} is missing a required "data" or "execute" property.`);
}
}
}
```
First, [`path.join()`](https://nodejs.org/api/path.html#pathjoinpaths) helps to construct a path to the `commands` directory. The first [`fs.readdirSync()`](https://nodejs.org/api/fs.html#fs_fs_readdirsync_path_options) method then reads the path to the directory and returns an array of all the folder names it contains, currently `['utility']`. The second `fs.readdirSync()` method reads the path to this directory and returns an array of all the file names they contain, 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 dynamically set each command into the `client.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
You will receive an interaction for every slash command executed. To respond to a command, you need to create a listener for the `interactionCreate` event that will execute code when your application receives an interaction. Place the code below in the `index.js` file you created earlier.
```js title="index.js" lineNumbers=32
// [!code ++:3]
client.on(Events.InteractionCreate, (interaction) => {
console.log(interaction);
});
```
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 `BaseInteraction#isChatInputCommand` method to exit the handler if another type is encountered. This method also provides typeguarding for TypeScript users, narrowing the type from `BaseInteraction` to a `ChatInputCommandInteraction`.
```js title="index.js" lineNumbers=32
client.on(Events.InteractionCreate, (interaction) => {
if (!interaction.isChatInputCommand()) return; // [!code ++]
console.log(interaction);
});
```
## Executing commands
When your bot receives a `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!".
```js title="commands/utility/ping.js"
// [!code word:execute]
module.exports = {
data: new SlashCommandBuilder().setName('ping').setDescription('Replies with Pong!'),
async execute(interaction) {
await interaction.reply('Pong!');
},
};
```
First, you need to get the matching command from the `client.commands` Collection based on the `interaction.commandName`. Your `Client` instance is always available via `interaction.client`. 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. In case something goes wrong, catch and log any error to the console.
```js title="index.js" lineNumbers=32
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
// [!code ++:23]
const command = interaction.client.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!',
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
flags: MessageFlags.Ephemeral,
});
}
}
});
```
#### 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.

View File

@@ -0,0 +1,215 @@
---
title: Event Handling
---
import { File, Folder, Files } from 'fumadocs-ui/components/files';
Node.js uses an event-driven architecture, making it possible to execute code when a specific event occurs. The discord.js library takes full advantage of this. You can visit the `Client` documentation to see the full list of events.
<Callout>
This page assumes you've followed the guide up to this point, and created your `index.js` and individual slash
commands according to those pages.
</Callout>
At this point, your `index.js` file has listeners for two events: `ClientReady` and `InteractionCreate`.
```js title="index.js"
// [!code word:ClientReady]
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
// [!code word:InteractionCreate]
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
const command = interaction.client.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!',
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
flags: MessageFlags.Ephemeral,
});
}
}
});
```
Currently, the event listeners are in the `index.js` file. `Client#ready`emits once when the `Client` becomes ready for use, and `Client#interactionCreate` emits whenever an interaction is received. Moving the event listener code into individual files is simple, and we'll be taking a similar approach to the command handler.
<Callout type="warn">
You're only going to move these two events from `index.js`. The code for [loading command
files](./handling-commands#loading-command-files) will stay here!
</Callout>
## Individual event files
Your project directory should look something like this:
<Files>
<Folder name="discord-bot" defaultOpen>
<Folder name="commands" defaultOpen />
<Folder name="node_modules" defaultOpen />
<File name="config.json" />
<File name="deploy-commands.js" />
<File name="index.js" />
<File name="package-lock.json" />
<File name="package.json" />
</Folder>
</Files>
Create an `events` folder in the same directory. You can then move the code from your event listeners in `index.js` to separate files: `events/ready.js` and `events/interactionCreate.js`.
```js tab="Ready Handler" title="events/ready.js"
const { Events } = require('discord.js');
module.exports = {
name: Events.ClientReady,
once: true,
execute(client) {
console.log(`Ready! Logged in as ${client.user.tag}`);
},
};
```
```js tab="Interaction Handler" title="events/interactionCreate.js"
const { Events, MessageFlags } = require('discord.js');
module.exports = {
name: Events.InteractionCreate,
async execute(interaction) {
if (!interaction.isChatInputCommand()) return;
const command = interaction.client.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!',
flags: MessageFlags.Ephemeral,
});
} else {
await interaction.reply({
content: 'There was an error while executing this command!',
flags: MessageFlags.Ephemeral,
});
}
}
},
};
```
```js title="index.js" tab="Main File (after removing events)"
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
const commandsPath = path.join(foldersPath, folder);
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 a required "data" or "execute" property.`);
}
}
}
client.login(token);
```
The `name` property states which event this file is for, and the `once` property holds a boolean value that specifies if the event should run only once. You don't need to specify this in `interactionCreate.js` as the default behavior will be to run on every event instance. The `execute` function holds your event logic, which will be called by the event handler whenever the event emits.
## Reading event files
Next, let's write the code for dynamically retrieving all the event files in the `events` folder. We'll be taking a similar approach to our [command handler](./handling-commands). Place the new code highlighted below in your `index.js`.
`fs.readdirSync().filter()` returns an array of all the file names in the given directory and filters for only `.js` files, i.e. `['ready.js', 'interactionCreate.js']`.
```js title="index.js"
const fs = require('node:fs');
const path = require('node:path');
const { Client, Collection, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.commands = new Collection();
const foldersPath = path.join(__dirname, 'commands');
const commandFolders = fs.readdirSync(foldersPath);
for (const folder of commandFolders) {
const commandsPath = path.join(foldersPath, folder);
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 a required "data" or "execute" property.`);
}
}
}
// [!code ++:12]
const eventsPath = path.join(__dirname, 'events');
const eventFiles = fs.readdirSync(eventsPath).filter((file) => file.endsWith('.js'));
for (const file of eventFiles) {
const filePath = path.join(eventsPath, file);
const event = require(filePath);
if (event.once) {
client.once(event.name, (...args) => event.execute(...args));
} else {
client.on(event.name, (...args) => event.execute(...args));
}
}
client.login(token);
```
You'll notice the code looks very similar to the command loading above it - read the files in the events folder and load each one individually.
The `Client` class in discord.js extends the [`EventEmitter`](https://nodejs.org/api/events.html#events_class_eventemitter) class. Therefore, the `client` object exposes the [`.on()`](https://nodejs.org/api/events.html#events_emitter_on_eventname_listener) and [`.once()`](https://nodejs.org/api/events.html#events_emitter_once_eventname_listener) methods that you can use to register event listeners. These methods take two arguments: the event name and a callback function. These are defined in your separate event files as `name` and `execute`.
The callback function passed takes argument(s) returned by its respective event, collects them in an `args` array using the `...` [rest parameter syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters), then calls `event.execute()` while passing in the `args` array using the `...` [spread syntax](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax). They are used here because different events in discord.js have different numbers of arguments. The rest parameter collects these variable number of arguments into a single array, and the spread syntax then takes these elements and passes them to the `execute` function.
After this, listening for other events is as easy as creating a new file in the `events` folder. The event handler will automatically retrieve and register it whenever you restart your bot.
<Callout>
In most cases, you can access your `client` instance in other files by obtaining it from one of the other discord.js
structures, e.g. `interaction.client` in the `interactionCreate` event. You do not need to manually pass it to your
events.
</Callout>

View File

@@ -0,0 +1,50 @@
---
title: The Main File
---
<Callout>
This page assumes you've already prepared the [configuration files](../app-creation/project-setup#configuration-files)
from the previous page. We're using the `config.json` approach, however feel free to substitute your own!
</Callout>
## Creating the main file
Open your code editor and create a new file. We suggest that you save the file as `index.js`, but you may name it whatever you wish.
Here's the base code to get you started:
```js title="index.js"
// Require the necessary discord.js classes
const { Client, Events, GatewayIntentBits } = require('discord.js');
const { token } = require('./config.json');
// Create a new client instance
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// When the client is ready, run this code (only once).
// The distinction between `client: Client<boolean>` and `readyClient: Client<true>` is important for TypeScript developers.
// It makes some properties non-nullable.
client.once(Events.ClientReady, (readyClient) => {
console.log(`Ready! Logged in as ${readyClient.user.tag}`);
});
// Log in to Discord with your client's token
client.login(token);
```
This is how you create a client instance for your Discord bot and log in to Discord. The `GatewayIntentBits.Guilds` intents option is necessary for the discord.js client to work as you expect it to, as it ensures that the caches for guilds, channels, and roles are populated and available for internal use.
<Callout>The term "guild" is used by the Discord API and in discord.js to refer to a Discord server.</Callout>
Intents also define which events Discord should send to your bot, and you may wish to enable more than just the minimum. You can read more about the other intents in the [Intents topic](../popular-topics/intents).
## Running your application
Open your terminal and run `node index.js` to start the process. If you see "Ready!" after a few seconds, you're good to go! The next step is to start adding slash commands to develop your app's functionality.
<Callout>
You can open your `package.json` file and edit the `"main": "index.js"` field to point to your main file. You can then run `node .` in your terminal to start the process!
After closing the process with <kbd>Ctrl</kbd> <kbd>C</kbd>, you can press the up arrow on your keyboard to bring up the latest commands you've run. Pressing up and then enter after closing the process is a quick way to start it up again.
</Callout>

View File

@@ -0,0 +1,11 @@
{
"title": "Creating Your App",
"pages": [
"project-setup",
"main-file",
"creating-commands",
"handling-commands",
"deploying-commands",
"handling-events"
]
}

View File

@@ -0,0 +1,101 @@
---
title: Project Setup
---
## Configuration files
Once you [add your bot to a server](../preparations/adding-your-app), the next step is to start coding and get it online! Let's start by creating a config file for your client token and a main file for your bot application.
As explained in the ["What is a token, anyway?"](../preparations/app-setup#what-is-a-token-anyway) section, your token is essentially your bot's password, and you should protect it as best as possible. This can be done through a `config.json` file or by using environment variables.
Open your application in the [Discord Developer Portal](https://discord.com/developers/applications) and go to the "Bot" page to copy your token.
## Using `config.json`
Storing data in a `config.json` file is a common way of keeping your sensitive values safe. Create a `config.json` file in your project directory and paste in your token. You can access your token inside other files by using `require()`.
```json tab="Config" title="config.json"
{
"token": "your-token-goes-here"
}
```
```js tab="Usage"
const { token } = require('./config.json');
console.log(token);
```
<Callout title="Danger" type="error">
If you're using Git, you should not commit files containing secrets. Read on to find out how to [exclude them from
versioning by using `.gitignore`](#git-and-gitignore).
</Callout>
## Using environment variables
Environment variables are, as the name suggets, values you can pass to your environment (e.g. terminal session, Docker container, node process). This has the benefit that you can keep your code the same for different execution contexts.
```txt title=".env"
A=Hello World
B=123
DISCORD_TOKEN=MTI3NDMxMjA3PDQ3ODIxNzIzNg.G6uEbl.IpA3-9YeScYr9pu9K1utMlpP4p-KJwNxcIAbi8
```
<Callout title="Danger" type="error">
If you're using Git, you should not commit `.env` or other environment files containing secrets. Read on to find out
how to [exclude them from versioning by using `.gitignore`](#git-and-gitignore).
</Callout>
To use environment variables in Node.js, we recommend you use the command line interface flag `--env-file` to point to the `.env` file you want to use. Note that the file name `.env` is just a convention. You could for example have a `.env.development` and `.env.production` file with different values depending on the Discord application you want to run your code.
You can also read multiple environment files by using the `--env-file` flag multiple times.
```sh
node --env-file=.env index.js
```
<Callout>You don't need to pass any special flags when using Bun! Bun reads `.env` files automatically.</Callout>
The values you specify in `.env` files this way are exposed through the `process.env` global variable in any file. Note that values passed this way will always be strings. If you want to do calculations on environment numbers, you will have to parse them:
```js title="index.js"
// [!code word:env]
console.log(process.env.A);
console.log(process.env.B + 9); // 1239 (this concatenates the number to the string!)
console.log(Number(process.env.C) + 9); // 132
console.log(process.env.DISCORD_TOKEN);
```
## Online editors
While we generally do not recommend using online editors as hosting solutions, but rather invest in a proper virtual private server, these services do offer ways to keep your credentials safe as well! Please see the respective service's documentation and help articles for more information on how to keep sensitive values safe:
<Cards>
<Card title="Glitch" href="https://help.glitch.com/s/article/Adding-Private-Data">
Learn more about storing secrets in `.env` files using Glitch
</Card>
<Card title="Heroku" href="https://devcenter.heroku.com/articles/config-vars">
Learn more about configuration variables in Heroku
</Card>
<Card title="Replit" href="https://docs.replit.com/replit-workspace/workspace-features/secrets#secrets">
Learn more about secrets and environment variables in Replit
</Card>
</Cards>
## Git and `.gitignore`
Git is a fantastic tool to keep track of your code changes and allows you to upload progress to services like [GitHub](https://github.com/), [GitLab](https://about.gitlab.com/), or [Bitbucket](https://bitbucket.org/product). While this is super useful to share code with other developers, it also bears the risk of uploading your configuration files with sensitive values!
You can specify files that Git should ignore in its versioning systems with a `.gitignore` file. Create a `.gitignore` file in your project directory and add the names of the files and folders you want to ignore. The following example ignores the `config.json` and `.env` files as well as the `node_modules` directory:
```txt title=".gitignore"
node_modules
.env
config.json
```
<Callout>
`.gitignore` files can specify intricate patterns and help with your general development flow. Apart from keeping your
credentials safe, you should exclude `node_modules` from version control as well, its contents can be restored from
the entries in your `package.json` and `package-lock.json` files.
</Callout>