This guide takes you from an empty server to a bot replying to /ping in your Discord, using discord.js — the most popular library for building bots in JavaScript. You'll deploy a working starter first, then take it apart so you understand every line, then make it yours.
| At a glance | |
|---|---|
| You need | a bot token from Your first Discord bot, and a Falix server running the Node.js application |
| Plan | free or premium — free runs while your session timer has time left, premium keeps it up 24/7 |
| Time | about fifteen minutes |
No server yet? See Create your first app server.
1. Deploy the starter
Use the template on this page. It writes index.js, package.json, .env, and a couple of helper files onto your server, overwriting only files with those names. If the server is currently running a different application, deploying switches it to Node.js first — that reinstalls the server and wipes its files, and the panel warns you before it does. On a fresh or already-Node server, nothing else is disturbed.
2. Paste in your token
Open the File Manager and edit .env. You'll see:
DISCORD_TOKEN=YOUR_TOKEN_HERE
Replace YOUR_TOKEN_HERE with the token you copied from the Developer Portal, save, and close. (More in Environment variables & secrets.)
💡 Tip: The
.envfile keeps your token out of your code and out of Git — never paste it directly intoindex.js.
3. Start it
Press Start on the Console page. On the first run the console shows npm install fetching discord.js and dotenv, then:
Listening as YourBot#0000
That line is your success signal. A bot makes only outbound connections to Discord, so there's no port to open; SERVER_PORT doesn't matter here.
🎯 Good to know: The word
Listeningis how the Node.js application flips your server's status to online — so your bot going live and the panel showing green are the same event.
4. Try it
In a channel of the server you invited the bot to, type /ping. It replies with the round-trip latency. If /ping doesn't appear in the command list yet, wait a few minutes — global slash commands can take a little while to register the very first time — then try again.
Want the bot in a second server? The invite URL you built in the Developer Portal isn't single-use — open it again and pick a different server (any where you have Manage Server). One running bot serves every server it's in; there's nothing to redeploy.
How it works
Here's the whole index.js the template deployed:
require('dotenv').config();
const { Client, GatewayIntentBits, Events } = require('discord.js');
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
client.once(Events.ClientReady, async (c) => {
await c.application.commands.set([
{ name: 'ping', description: 'Check that the bot is alive' },
]);
console.log(`Listening as ${c.user.tag}`);
});
client.on(Events.InteractionCreate, async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
await interaction.reply(`Pong! ${client.ws.ping} ms`);
}
});
client.login(process.env.DISCORD_TOKEN);
Five moving parts:
require('dotenv').config()reads the.envfile and loadsDISCORD_TOKENintoprocess.envso the code can use it without hard-coding the secret.- Intents —
GatewayIntentBits.Guildsis all a slash-command bot needs. You add more intents only when your code handles more kinds of events, and privileged ones (like Message Content) must also be switched on in the Developer Portal. ClientReadyfires once, after login succeeds. It callsapplication.commands.set([...])to register the bot's slash commands with Discord — that's what makes/pingshow up when someone types/.InteractionCreatefires whenever someone runs a command. The handler checks it's a slash command, matches oncommandName, and replies.client.login(process.env.DISCORD_TOKEN)connects using the token from.env. This is the call that either succeeds (Listening as …) or fails on a bad token.
Make it yours: add a second command
Slash commands live in two places, and adding one means editing both. Say you want /roll.
First, register it in the ClientReady handler by adding a line to the array:
await c.application.commands.set([
{ name: 'ping', description: 'Check that the bot is alive' },
{ name: 'roll', description: 'Roll a six-sided die' },
]);
Then answer it by adding a branch to InteractionCreate:
if (interaction.commandName === 'roll') {
const result = Math.floor(Math.random() * 6) + 1;
await interaction.reply(`🎲 You rolled a ${result}`);
}
Save and restart. After the usual first-time delay, /roll appears alongside /ping. Every new command follows this exact shape: describe it in the set([...]) list, handle it in InteractionCreate.
Adding packages
Reach for a library through the Packages page in your server menu: type the name into the Install package panel, press Install, and your package.json is updated for you; restart so your bot loads it. Editing package.json by hand works too (it installs on every start), but the Packages page is the easy path and flags outdated or insecure dependencies.
Troubleshooting
TokenInvalidin the console — the token in.envis wrong or was reset. Copy a fresh one from the Developer Portal (resetting invalidates the old) and paste it in again. See Bot appears offline.Used disallowed intents— your code requests a privileged intent that isn't enabled. Toggle it on the Bot page in the Developer Portal, or remove it from theintentsarray if you don't need it./pingnever appears — global commands lag on first registration; give it a few minutes. Still missing? Re-invite the bot with theapplications.commandsscope included.- Bot is online but ignores commands — that's the
InteractionCreatehandler. Make sure the listener is still there and thatcommandNamematches the name you registered exactly. - Online but misbehaving another way — when the green dot is on but a command throws or does nothing, the answer is in the Console: an error and stack trace print there, below the
Listening as …line, the moment your handler fails. Read it, fix the code, and restart.