{"post":{"slug":"how-to-build-a-discord-bot-2026","title":"How to build a Discord bot in 2026","excerpt":"Discord bots in 2026 use slash commands, interactions, and gateway intents. This guide covers a production-ready setup with discord.js v15 in under an hour.","category":"code","date":"February 22, 2026","author":"ByteLedger Team","content":"<span class=\"dropcap\">D</span>iscord bots in 2026 are interaction-first. The old prefix-command era (`!help`, `!ban`) is over for verified bots — Discord now requires slash commands, context menus, and modal forms delivered through its interaction system. The good news: `discord.js` v15 makes the wiring straightforward, and a minimal but production-capable bot takes less than an hour to ship.\n\n## What changed in 2026\n\n- **discord.js v15** is the stable release, targeting Node.js 20+ with full ESM support and a revamped `Client` options API.\n- **Privileged intents require justification.** `GuildMembers` and `MessageContent` are privileged — enable only what you genuinely need or your bot won't pass verification.\n- **Application commands are the only supported public API.** Slash commands, user context menus, and message context menus are all registered as `ApplicationCommand` objects.\n- **Interactions have a 3-second acknowledgement window.** Use `interaction.deferReply()` for any async work; otherwise Discord shows \"interaction failed.\"\n\n## Project setup\n\n```bash\nmkdir my-discord-bot && cd my-discord-bot\nnpm init -y\nnpm install discord.js@15\n```\n\nCreate `index.mjs`:\n\n```js\nimport { Client, GatewayIntentBits, REST, Routes } from 'discord.js';\n\nconst client = new Client({\n  intents: [GatewayIntentBits.Guilds],\n});\n\nclient.once('ready', () => console.log(`Logged in as ${client.user.tag}`));\nclient.login(process.env.DISCORD_TOKEN);\n```\n\n`GatewayIntentBits.Guilds` is the minimum required intent for slash commands. Add others only when you need them.\n\n## Registering slash commands\n\nDefine commands as plain objects matching the `ApplicationCommandData` shape:\n\n```js\nconst commands = [\n  {\n    name: 'ping',\n    description: 'Replies with the current gateway latency.',\n  },\n  {\n    name: 'echo',\n    description: 'Echoes your message.',\n    options: [\n      { name: 'text', description: 'Text to echo', type: 3, required: true },\n    ],\n  },\n];\n\nconst rest = new REST().setToken(process.env.DISCORD_TOKEN);\n\nawait rest.put(\n  Routes.applicationCommands(process.env.APP_ID),\n  { body: commands },\n);\nconsole.log('Commands registered globally.');\n```\n\nGlobal registration propagates in up to ~1 hour. For development, use `Routes.applicationGuildCommands(appId, guildId)` — it applies instantly.\n\n## Handling interactions\n\n```js\nconst handlers = new Map([\n  ['ping', async (i) => i.reply(`Pong! Latency: ${client.ws.ping}ms`)],\n  ['echo', async (i) => i.reply(i.options.getString('text', true))],\n]);\n\nclient.on('interactionCreate', async (interaction) => {\n  if (!interaction.isChatInputCommand()) return;\n  const handler = handlers.get(interaction.commandName);\n  if (!handler) return interaction.reply({ content: 'Unknown command.', ephemeral: true });\n\n  try {\n    await handler(interaction);\n  } catch (err) {\n    console.error(err);\n    const payload = { content: 'Something went wrong.', ephemeral: true };\n    interaction.replied ? interaction.followUp(payload) : interaction.reply(payload);\n  }\n});\n```\n\nThe `Map`-based dispatcher keeps `interactionCreate` clean and makes each handler independently testable.\n\n## What changed in 2026\n\n| Feature | Pre-2024 | 2026 |\n|---|---|---|\n| Commands | Prefix (`!cmd`) or slash | Slash + context menus only (verified bots) |\n| Framework | discord.js v13/v14 | discord.js v15 (ESM-first) |\n| Node.js target | 16+ | 20+ |\n| Intent gating | Loose | Strict — privileged require approval |\n| Interaction timeout | 3s | 3s (defer for async) |\n\n## How to pick gateway intents\n\n| Intent | Need it when |\n|---|---|\n| `Guilds` | Any slash command or guild info — always |\n| `GuildMessages` | Reading messages (requires approval after 100 servers) |\n| `MessageContent` | Reading message body — privileged, avoid if possible |\n| `GuildMembers` | Listing or watching member join/leave — privileged |\n| `DirectMessages` | DM support |\n\nStart with `Guilds` only. Add intents one at a time as your feature set demands.\n\n## How to start\n\n1. Create an application at [discord.com/developers/applications](https://discord.com/developers/applications).\n2. Under **Bot**, generate a token and enable only the intents your bot needs.\n3. Generate an OAuth2 URL with the `bot` + `applications.commands` scopes.\n4. Set `DISCORD_TOKEN` and `APP_ID` in your environment.\n5. Run your registration script once, then start the bot.\n\n## Common mistakes\n\n**Not deferring long-running handlers.** Discord closes the interaction after 3 seconds. Call `await interaction.deferReply()` at the top of any handler that hits a DB or external API.\n\n**Registering commands on every boot.** Global registration has a rate limit. Register once (or on a deploy hook), not inside `client.once('ready')`.\n\n**Catching errors silently.** An unhandled rejection inside an event listener crashes the process in Node 20. Always wrap handlers in try/catch and log.\n\n**Using outdated `MessageEmbed`.** It was renamed `EmbedBuilder` in v14. v15 will throw at runtime if you use the old name.\n\n## What to skip\n\n- **Prefix command parsing** — adds complexity and fails bot verification at scale.\n- **Heavy bot frameworks** (Sapphire, etc.) until you understand the underlying discord.js primitives. They solve real problems at scale, but obscure the model early on.\n- **Storing the bot token in `.env` committed to git** — use a secrets manager or environment injection in CI.\n\n## FAQ\n\n**Do I need a server to run a Discord bot?**\nYes for 24/7 uptime. A ~$5/month VPS or a free Railway/Render instance works fine for small bots. Discord bots use a persistent WebSocket, not HTTP polling.\n\n**How do I make commands guild-only vs. global?**\nUse `Routes.applicationGuildCommands(appId, guildId)` to register to one guild (instant) or `Routes.applicationCommands(appId)` for all guilds (up to 1-hour propagation).\n\n**Can I use TypeScript?**\nYes — discord.js v15 ships full type definitions. Use `ts-node` or compile to ESM with `tsc`. The types for `CommandInteraction` and option accessors are especially helpful.\n\n**How do I handle button clicks and modals?**\nUse `interaction.isButton()` and `interaction.isModalSubmit()` checks inside `interactionCreate`. Store state in a `Map` keyed on `interaction.customId`.\n\n## Where to go next\n\n- [How to build a Slack app in 2026](/blog/how-to-build-a-slack-app-2026)\n- [How to build a Telegram bot in 2026](/blog/how-to-build-a-telegram-bot-2026)\n- [How to add authentication to an app in 2026](/blog/how-to-add-authentication-to-an-app-2026)","hue":305,"metaTitle":"How to Build a Discord Bot in 2026 (discord.js v15)","metaDescription":"Step-by-step guide to building a Discord bot in 2026 with discord.js v15, slash commands, gateway intents, and interaction handlers.","keywords":["how to build a discord bot 2026","discord.js v15 tutorial","discord slash commands 2026","discord bot interactions","discord bot gateway intents","discord bot deployment 2026","discord application commands","node.js discord bot","discord bot slash command handler","discord bot setup 2026"],"tldr":["**Use slash commands, not prefix commands** — Discord deprecated prefix-based bots for verified bots; interactions-based flow is the 2026 default.","**Register commands via the REST API at startup** — use `client.application.commands.set()` or the REST endpoint to deploy globally or per-guild.","**Enable only the gateway intents you need** — omitting unused intents keeps your bot verified and avoids rate-limit friction.","**Handle interactions with a command map** — a `Map<string, CommandHandler>` scales cleanly to dozens of commands without if-else chains.","**Skip:** listening for raw `messageCreate` events to parse \"!command\" strings — it breaks verification and ignores modern Discord UX."],"readTime":"4 min read","wordCount":889},"related":[{"slug":"how-to-build-a-slack-app-2026","title":"How to build a Slack app in 2026","excerpt":"Slack apps in 2026 use Bolt, socket mode for dev, and event subscriptions for production. This guide gets you from zero to a working slash command in under an hour.","category":"code","date":"March 28, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":307},{"slug":"how-to-build-a-telegram-bot-2026","title":"How to build a Telegram bot in 2026","excerpt":"Telegram bots use the Bot API over webhooks or long-polling. This guide covers a production-ready setup with grammY in 2026, including commands and middleware.","category":"code","date":"March 11, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":306},{"slug":"how-to-make-a-discord-bot-2026","title":"How to Make a Discord Bot in 2026: a beginner walkthrough","excerpt":"Making a Discord bot in 2026 takes an app registration, a token, a library, and a few lines of code. Here is the full beginner walkthrough and pitfalls.","category":"code","date":"May 11, 2026","author":"ByteLedger Team","readTime":"4 min read","hue":205}],"sameCategory":[{"slug":"bazel-build-explained-2026","title":"Bazel Explained: What Hermetic Builds Actually Cost","excerpt":"Bazel delivers reproducible, precisely cached builds and demands that you describe everything. Here is what you get, what it costs, and who should say no.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":145},{"slug":"graphql-federation-2026","title":"GraphQL Federation: One Graph, Many Teams","excerpt":"Federation composes independently-owned subgraphs into a single API. Here is what it solves, the operational cost it adds, and when a simpler gateway is enough.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":300},{"slug":"hermetic-builds-explained-2026","title":"Hermetic Builds Explained: Same Inputs, Same Output","excerpt":"A hermetic build depends only on declared inputs. Here is what that property gives you, the common leaks that break it, and how to move toward it incrementally.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":130},{"slug":"monorepo-tooling-compared-2026","title":"Monorepo Tooling Compared: What You Actually Need","excerpt":"Monorepo tools solve task orchestration and caching, not repository layout. Here is what each tier provides and how to tell when you have outgrown the simple option.","category":"code","date":"August 19, 2026","author":"ByteLedger Team","readTime":"3 min read","hue":150}],"newer":{"slug":"how-to-build-a-cli-2026","title":"How to build a CLI in 2026: tools, patterns, and pitfalls","excerpt":"Command-line tools remain one of the highest-leverage things a developer can build. Here is the 2026 guide to building CLIs in Node.js that people actually want to use.","category":"code","date":"February 22, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":305},"older":{"slug":"fixed-vs-variable-rate-2026","title":"Fixed vs variable rate in 2026: which loan or mortgage rate should you choose?","excerpt":"Fixed rates give certainty; variable rates offer a lower starting point with future risk. In 2026, the right choice depends on your timeline and rate outlook.","category":"finance","date":"February 21, 2026","author":"ByteLedger Team","readTime":"5 min read","hue":316}}