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, runnpm 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
startscript, 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.contentis 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
importin your code should come with"type": "module"inpackage.json. Current Node versions run the file without it but warn (MODULE_TYPELESS_PACKAGE_JSON) on every start; with"type": "commonjs", or in a.cjsfile, it fails withCannot use import statement outside a module.requirein your code with"type": "module"set fails withrequire is not defined in ES module scope.- Some packages dropped CommonJS in their newer versions (
node-fetch3 is the famous one). Recent Node versions canrequire()most of them anyway; if you still getERR_REQUIRE_ESM, useimportor the older major version. Fornode-fetch, you do not need it at all:fetchis 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
- 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.
- In the panel, paste your token into Bot token on the Startup tab.
- Upload your code and
package.jsonin Files, or paste your GitHub repository link into Git repository. - Press Restart. Packages install from your lock file with npm, yarn or pnpm, and your
startscript 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.