How to make a Discord bot in JavaScript (discord.js)

Make a Discord bot in JavaScript from scratch with discord.js and Node.js: create the app, write slash commands, add a welcome message, test it locally and host it for free on Torchlite.

4 min readBy Torchlite teamUpdated

How to make a Discord bot in JavaScript (discord.js)
On this page

This tutorial builds a small but real Discord bot in JavaScript with discord.js on Node.js: two slash commands, /hello and /add, and a message that welcomes new members. You test it on your own computer, then put it on a free Torchlite Bot server so it stays online without your computer.

You need Node.js 22 or newer and a Discord account. No earlier bot experience is needed.

Create the bot in Discord

  1. Open the Discord Developer Portal and click New Application. The name you give it is the bot's name.
  2. Open Bot in the left menu and click Reset Token. Copy the token and keep it somewhere private.
  3. Further down on the same page, switch on Server Members Intent. The welcome message needs it; the slash commands do not.
  4. Open OAuth2, then URL Generator. Tick bot and applications.commands, and under Bot Permissions tick Send Messages.
  5. Open the generated URL, pick your Discord server and click Authorize.

Set up the project

Make a folder for the bot and install discord.js in it:

bash
npm init -y
npm install discord.js

That creates package.json, which lists discord.js as a dependency, and a node_modules folder you never upload or commit.

Write the bot

Create index.js:

js
const { Client, Events, GatewayIntentBits, SlashCommandBuilder } = require('discord.js');

const client = new Client({
    // Guilds is enough for slash commands; GuildMembers adds join events
    // and is switched on in the portal.
    intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],
});

const commands = [
    new SlashCommandBuilder().setName('hello').setDescription('Say hello'),
    new SlashCommandBuilder()
        .setName('add')
        .setDescription('Add two numbers')
        .addIntegerOption((o) => o.setName('a').setDescription('The first number').setRequired(true))
        .addIntegerOption((o) => o.setName('b').setDescription('The second number').setRequired(true)),
];

client.once(Events.ClientReady, async (ready) => {
    // Sends the slash commands to Discord once, at startup.
    await ready.application.commands.set(commands.map((c) => c.toJSON()));
    console.log(`Online as ${ready.user.tag} in ${ready.guilds.cache.size} server(s).`);
});

client.on(Events.InteractionCreate, async (interaction) => {
    if (!interaction.isChatInputCommand()) return;

    if (interaction.commandName === 'hello') {
        await interaction.reply(`Hello, ${interaction.user}!`);
    } else if (interaction.commandName === 'add') {
        const a = interaction.options.getInteger('a');
        const b = interaction.options.getInteger('b');
        await interaction.reply(`${a} + ${b} = ${a + b}`);
    }
});

client.on(Events.GuildMemberAdd, async (member) => {
    const channel = member.guild.systemChannel;
    if (channel) await channel.send(`Welcome to ${member.guild.name}, ${member}!`);
});

client.login(process.env.DISCORD_TOKEN);

What each part does:

  • Intents tell Discord which events the bot wants. Guilds covers slash commands; GuildMembers adds join events.
  • SlashCommandBuilder describes each command and its options. commands.set registers them with Discord when the bot is ready.
  • InteractionCreate fires when someone uses a command. options.getInteger reads the numbers, already checked by Discord.
  • GuildMemberAdd runs when someone joins and writes in the server's system channel, the one set under Server Settings, Overview.
  • The token comes from the DISCORD_TOKEN environment variable, never from the code.

Run it on your computer

Set the token for this terminal session, then start the bot. On macOS or Linux:

bash
export DISCORD_TOKEN="paste-your-token-here"
node index.js

On Windows PowerShell:

powershell
$env:DISCORD_TOKEN="paste-your-token-here"
node index.js

The terminal prints Online as YourBot#1234 in 1 server(s). Type / in your Discord server and the two commands appear. The first time, slash commands can take a few minutes to show up.

Host it for free on Torchlite

Your bot is offline as soon as you close the terminal. To keep it running:

  1. On Create your server, pick the Bot plan and Discord bot (Node.js).
  2. In the panel, open Startup and paste the token into Bot token.
  3. Open Files, delete the starter bot's files, and upload your index.js and package.json, without node_modules. Check that Main file under Startup is index.js.
  4. Press Start in the Console. The server installs discord.js from package.json and starts your bot.

To deploy from GitHub instead, so a push and a restart is your whole deploy, follow the Discord bot guide. The Bot plan gives 512 MB of RAM and asks for a Renew once every 24 hours; Discord bot hosting has the details.

Keep it healthy

A few habits save most of the trouble once real people use the bot:

  • Answer within 3 seconds. Discord shows "The application did not respond" if a command is not acknowledged in time. For anything slow, such as a web request or a database query, call await interaction.deferReply() first, then send the result with await interaction.editReply(...).
  • Catch errors per command. Wrap risky code in try and catch, and reply with a short message instead of letting the command fail silently. Log the error with console.error, so it shows up in the panel's Console.
  • Keep data on disk, not in memory. Everything in JavaScript variables is lost when the bot restarts, and on a free server it restarts whenever a renew is missed. Store anything that matters in the MySQL database or an SQLite file.
  • Do not flood channels. Sending many messages in a loop runs into Discord's rate limits; discord.js queues them, but your bot looks frozen in the meantime. Batch the text into one message instead.
  • Watch the memory. 512 MB is plenty, but caching every member of a big server adds up. Only ask for the intents you actually use.

Where to go next

  • More commands: add another builder to commands and another branch in InteractionCreate. Options exist for strings, numbers, users, roles and channels.
  • Commands only admins can use: add .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) to a builder, importing PermissionFlagsBits from discord.js.
  • A database: your Bot server includes a MySQL database, or keep it simple with an SQLite file.
  • Split it up: when the file grows, put each command in its own file and load them from a folder, as the discord.js guide shows.

Common problems

"An invalid token was provided" or TokenInvalid

The environment variable is empty or has a space in it, or on Torchlite the Bot token field is empty. Copy the token again and restart.

"Used disallowed intents"

The code asks for GuildMembers, but Server Members Intent is off in the Developer Portal. Switch it on, or remove GuildMembers and the welcome message.

The slash commands do not appear

Wait a few minutes after the first start, and check that the invite URL included applications.commands. Restart the bot once.

Stuck on a step?

Ask on the forum. Your question links back to this guide, so whoever answers knows where you are.

Ask about this guide

About the author

Torchlite team

The people who run Torchlite

We build and run the Torchlite panel and the nodes. Every guide is checked on our own servers before it goes up, and updated when the panel changes.

Found a mistake? Tell us on Discord

Related guides

All guides →