Docs academy · Guide 2 of 8 · 12 min read
Your first bot in discord.js
Build a working bot with a slash command in JavaScript, using discord.js v14.
By the end of this guide you'll have a bot online that answers a /ping command. You need the application, token and test server from Start here.
Set up the project
Install the current LTS version of Node.js, then check it from a terminal:
node --version
Create a project and install discord.js:
mkdir my-bot
cd my-bot
npm init -y
npm install discord.js
Open package.json and add this line, so that you can use modern import syntax:
"type": "module",
Create your .env file next to package.json. DISCORD_GUILD_ID is your test server's ID. To get it, turn on Developer Mode in Discord's advanced settings, then right-click your server and click Copy Server ID.
DISCORD_TOKEN=paste-your-token-here
DISCORD_CLIENT_ID=your-application-id
DISCORD_GUILD_ID=your-test-server-id
Add .env and node_modules/ to a .gitignore file now, before you forget.
Register a slash command
Discord needs to know about a command before people can use it. Registering is a separate step from running the bot, so it gets its own small script. Save this as deploy-commands.js:
import { REST, Routes, SlashCommandBuilder } from "discord.js";
const commands = [
new SlashCommandBuilder().setName("ping").setDescription("Check that the bot is alive"),
].map((command) => command.toJSON());
const rest = new REST().setToken(process.env.DISCORD_TOKEN);
await rest.put(
Routes.applicationGuildCommands(process.env.DISCORD_CLIENT_ID, process.env.DISCORD_GUILD_ID),
{ body: commands },
);
console.log(`Registered ${commands.length} command(s)`);
Run it once. Node reads the .env file for you with --env-file:
node --env-file=.env deploy-commands.js
You only need to run it again when you add a command or change its name or options.
Write the bot
Save this as index.js:
import { Client, Events, GatewayIntentBits } from "discord.js";
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, (ready) => {
console.log(`Logged in as ${ready.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "ping") {
await interaction.reply(`Pong! Gateway latency: ${client.ws.ping} ms`);
}
});
client.login(process.env.DISCORD_TOKEN);
Start it:
node --env-file=.env index.js
When you see Logged in as, your bot is online. Go to your test server and type /ping.
What just happened
intentstells Discord which events to send you.Guildsis the minimum almost every bot needs. Guide 4 covers the rest.Events.ClientReadyfires once the connection is up.Events.InteractionCreatefires when someone uses a slash command, button or menu.interaction.replyanswers the command. Discord expects a reply within 3 seconds.
If your command needs longer than 3 seconds, say so first, then answer when you're done:
await interaction.deferReply();
const result = await doSlowWork();
await interaction.editReply(result);
Don't let errors kill your bot
One failed command shouldn't take everything down. Wrap your command code, and tell the user when something goes wrong:
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
try {
if (interaction.commandName === "ping") {
await interaction.reply("Pong!");
}
} catch (error) {
console.error(error);
const message = { content: "Something went wrong running that command.", ephemeral: true };
if (interaction.replied || interaction.deferred) await interaction.followUp(message);
else await interaction.reply(message);
}
});
Common problems
| What you see | What it usually means |
| --- | --- |
| An invalid token was provided | The token is wrong, or .env wasn't loaded. Check that you ran with --env-file=.env. |
| /ping doesn't appear | You didn't run deploy-commands.js, or the invite link was missing applications.commands. |
| "The application did not respond" | Your bot isn't running, or it took more than 3 seconds without deferring. |
| Used disallowed intents | You requested a privileged intent that isn't switched on in the Developer Portal. |
Next
Your bot works, but only one command is registered and only in your test server. Continue with Slash commands, intents and permissions.