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

  • intents tells Discord which events to send you. Guilds is the minimum almost every bot needs. Guide 4 covers the rest.
  • Events.ClientReady fires once the connection is up.
  • Events.InteractionCreate fires when someone uses a slash command, button or menu.
  • interaction.reply answers 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.

Still stuck? Open a ticket and a mentor will take a look. We'll know which guide you were on.