Files
bun-src/docs/guides/ecosystem/discordjs.mdx
T
2026-08-27 21:09:14 +00:00

163 lines
6.1 KiB
Plaintext

---
title: Create a Discord bot
sidebarTitle: "Discord.js with Bun"
mode: center
---
Discord.js runs on Bun with no extra setup. This guide builds a bot that answers a `/ping` slash command: you register the command once, then start the bot and use it in your server. If this is your first bot, copy each block as you reach it.
---
Create a folder for your bot and set it up with `bun init`. Pick the defaults when it asks.
```sh terminal icon="terminal"
mkdir my-bot
cd my-bot
bun init
```
---
Add Discord.js to the project.
```sh terminal icon="terminal"
bun add discord.js
```
---
Your bot needs its own account, which you create in Discord's developer portal. Open the [developer portal](https://discord.com/developers/applications), sign in, and create an **Application**. Discord adds a bot user to every new application automatically; it's on the **Bot** tab. The Discord.js guide's [setup walkthrough](https://discordjs.guide/legacy/preparations/app-setup) has screenshots if you get lost.
Copy two values from the portal:
- The **token** on the **Bot** tab: click **Reset Token** to generate it (Discord only shows it once). It's the password your code uses to log in, so treat it like one and keep it to yourself.
- The **Application ID** on the **General Information** tab. Discord uses it to tie your commands to this app.
---
A bot can't do anything in a server until you invite it. In the portal's **OAuth2** section, generate an invite URL with the `bot` and `applications.commands` scopes, open it, and add the bot to a server you manage. Slash commands need the `applications.commands` scope, so include it. A personal server you make for testing is a good place to start, and the Discord.js [guide to adding a bot](https://discordjs.guide/legacy/preparations/adding-your-app) walks through it with screenshots.
---
You also need your server's ID to register the command there. In Discord, turn on **Settings > Advanced > Developer Mode**, then right-click your server's icon and choose **Copy Server ID**.
---
Save all three values in `.env.local`. Bun reads this file on startup and loads it into `process.env`, so nothing secret lives in your code.
```ini .env.local icon="settings"
DISCORD_TOKEN=your-bot-token
DISCORD_CLIENT_ID=your-application-id
DISCORD_GUILD_ID=your-server-id
```
---
Add `.env.local` to your `.gitignore` before you commit anything. Anyone who reads the token can control your bot, so the token should never land in version control.
```txt .gitignore icon="file-code"
node_modules
.env.local
```
---
Discord has to know about a command before anyone can use it. Register `/ping` with a short script named `deploy-commands.ts`.
```ts deploy-commands.ts icon="/icons/typescript.svg"
import { REST, Routes, SlashCommandBuilder } from "discord.js";
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID } = process.env;
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DISCORD_GUILD_ID) {
throw new Error("Set DISCORD_TOKEN, DISCORD_CLIENT_ID, and DISCORD_GUILD_ID in .env.local");
}
// the commands you want to register
const commands = [new SlashCommandBuilder().setName("ping").setDescription("Replies with Pong!").toJSON()];
const rest = new REST().setToken(DISCORD_TOKEN);
// register them in your test server
await rest.put(Routes.applicationGuildCommands(DISCORD_CLIENT_ID, DISCORD_GUILD_ID), { body: commands });
console.log("Registered /ping");
```
Run it once.
```sh terminal icon="terminal"
bun run deploy-commands.ts
```
You only run this again when you add a command or change its name or description, not every time the bot starts. Registering to your server instead of globally keeps the command scoped to where you're testing.
---
Now the bot itself. Save it as `bot.ts`.
```ts bot.ts icon="/icons/typescript.svg"
import { Client, Events, GatewayIntentBits } from "discord.js";
const { DISCORD_TOKEN } = process.env;
if (!DISCORD_TOKEN) {
throw new Error("Set DISCORD_TOKEN in .env.local");
}
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
// runs once, right after the bot connects
client.once(Events.ClientReady, readyClient => {
console.log(`Logged in as ${readyClient.user.tag}`);
});
// runs every time someone uses a slash command
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === "ping") {
await interaction.reply("Pong!");
}
});
client.login(DISCORD_TOKEN);
```
The ready handler logs a line once the bot connects. After that, `interactionCreate` runs whenever someone uses a slash command; it confirms the command was `/ping` and replies with `Pong!`.
---
Start the bot with `bun run`.
```sh terminal icon="terminal"
bun run bot.ts
```
The first connection takes a few seconds. Once the login line prints, switch to Discord and type `/ping` in your server.
```txt
Logged in as my-bot#1234
```
The bot replies with `Pong!`. You've got a working Discord bot.
---
To add another command, define it in `deploy-commands.ts`, run that script again, and add an `if` branch for its name in `bot.ts`. The [Discord.js docs](https://discord.js.org/docs) cover command options, permissions, buttons, embeds, and the rest of the API.
---
When you deploy, there's no build or bundling step. Bun runs `bot.ts` and every file it imports directly, so you ship your source as-is and start it with the same `bun run bot.ts` you use while developing.
`deploy-commands.ts` registers `/ping` in your test server, which is the right scope while you're building. To publish the bot to every server it joins, register globally instead: change the route to `Routes.applicationCommands(DISCORD_CLIENT_ID)`. Global registration doesn't use a server, so you can also drop `DISCORD_GUILD_ID` from the script's check and from `.env.local`.
To keep the bot online and bring it back after a crash or reboot, run it under a process manager.
<Columns cols={2}>
<Card title="systemd" href="/guides/ecosystem/systemd" icon="server">
Run your bot as a Linux daemon
</Card>
<Card title="PM2" href="/guides/ecosystem/pm2" icon="cog">
Manage your bot with PM2
</Card>
</Columns>