Guide

How to host a discord.js bot

A discord.js bot that runs fine with node index.js on your computer usually breaks on a server for one of five reasons. None of them are about the host.

7 minute read

1. Take the token out of the code

Read it from an environment variable. The same code then runs on your computer and on a server, and the token never ends up in a file you upload or a repository you push.

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

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

client.once(Events.ClientReady, (c) => {
  console.log(`Logged in as ${c.user.tag}`);
});

client.login(process.env.BOT_TOKEN);

To run it locally, keep the token in a .env file (and add .env to .gitignore). Node 20.6 and newer can read it without any package:

node --env-file=.env index.js

That is for your own computer. On a server that gives the bot its token some other way, --env-file stops with .env: not found, so keep it out of the start script (or use the dotenv package, which ignores a missing file).

If BOT_TOKEN is missing, discord.js throws TokenInvalid: An invalid token was provided, which is the same error as a wrong token. Check the variable is set before you check the token.

2. Make package.json complete

A host installs exactly what package.json lists. Anything you installed globally, or installed without saving, is missing on the server and fails with Cannot find module.

  • Delete node_modules, run npm install, and start the bot. If it starts, your dependencies are all listed.
  • Commit or upload package-lock.json. It pins every version, so the server installs what you tested.
  • Never upload node_modules. Some packages contain compiled code for your operating system and will not load on Linux.
  • Add a start script, so any host knows how to run it:
    "scripts": {
      "start": "node src/index.js"
    }

3. Match your intents to the portal

Reading message text needs GatewayIntentBits.MessageContent in your code and Message Content Intent switched on in the Developer Portal under Bot. The same goes for GuildMembers and GuildPresences.

  • In the code but not the portal: the bot crashes on login with Used disallowed intents.
  • In the portal but not the code: the bot connects, and message.content is an empty string.

Only ask for what you use. Every intent means more events and more memory.

4. Register slash commands once, not on every start

Many tutorials register commands in a separate deploy-commands.js you run by hand. That is fine. What is not fine is registering them with a loop of API calls every time the bot starts: a bot that crashes and restarts repeatedly can hit Discord's limit on command creation (200 a day per guild). Use one bulk call, client.application.commands.set([...]), which replaces the whole list in a single request.

Global commands can take a little while to show up the first time. For testing, register them to one guild with guild.commands.set([...]), which is instant.

5. Pick ES modules or CommonJS, not both

  • import in your code should come with "type": "module" in package.json. Current Node versions run the file without it but warn (MODULE_TYPELESS_PACKAGE_JSON) on every start; with "type": "commonjs", or in a .cjs file, it fails with Cannot use import statement outside a module.
  • require in your code with "type": "module" set fails with require is not defined in ES module scope.
  • Some packages dropped CommonJS in their newer versions (node-fetch 3 is the famous one). Recent Node versions can require() most of them anyway; if you still get ERR_REQUIRE_ESM, use import or the older major version. For node-fetch, you do not need it at all: fetch is built into Node 18 and newer.

Where to run it

The keeping a bot online 24/7 guide compares the options. On your own Linux machine or VPS, a process manager does the job: pm2 start npm --name mybot -- start and pm2 save, or a systemd unit like the one in the systemd guide with ExecStart=/usr/bin/node src/index.js.

Running it on SnowServers

  1. Pick JavaScript and a Node.js version (20, 22 or 24) at checkout. Flurry, 1 GB, fits most bots and has a 7-day free trial.
  2. In the panel, paste your token into Bot token on the Startup tab.
  3. Upload your code and package.json in Files, or paste your GitHub repository link into Git repository.
  4. Press Restart. Packages install from your lock file with npm, yarn or pnpm, and your start script runs.

If it crashes, the console adds a plain-English hint for most of the errors above, and it can message you in Discord. Our JavaScript docs have the details.