← Blog

How to Build a Claude-Powered Discord Bot

2026-10-30 · 5 min read · SubToAPI Team

Building a Claude-powered Discord bot comes down to three pieces: a Discord application with a bot token, a way to call Claude over HTTPS, and a small amount of glue code that forwards messages back and forth. You don't need a framework or a queue system to get started — a single Node.js process with discord.js and a fetch call is enough for most servers.

This guide walks through the whole setup: registering the bot, wiring up Claude for replies, handling streaming so the bot doesn't feel laggy, and adding basic memory so conversations don't reset every message. By the end you'll have a working bot you can drop into any server.

What you need before starting

Step 1: Create the Discord application

Go to the Discord Developer Portal, create a new application, and add a Bot user to it. Copy the bot token — you'll need it as an environment variable. Under OAuth2 → URL Generator, select the bot scope and permissions for Send Messages, Read Message History, and Use Slash Commands, then use the generated URL to invite the bot to your server.

Enable the Message Content Intent under the Bot settings — without it, your bot can't read what users type in regular channel messages.

Step 2: Set up Claude access

If you already have an Anthropic API key, you can skip this. If you want a quicker path with built-in request logging, rate limit handling, and a dashboard to see token usage per command, sign up at /signup and generate a key. Plans start at €9/month for solo projects, scaling to team seats if you end up running the bot across multiple servers — see /pricing.

The request format follows the standard Claude messages API shape, documented at /docs/messages:

curl https://api.subtoapi.app/v1/messages \
  -H "Authorization: Bearer $SUBTOAPI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4-5",
    "max_tokens": 500,
    "messages": [
      {"role": "user", "content": "Explain Discord slash commands in one sentence."}
    ]
  }'

Step 3: Wire up the bot

Install the dependencies:

npm install discord.js dotenv

Create a .env file with DISCORD_TOKEN and SUBTOAPI_KEY (or ANTHROPIC_API_KEY if calling Claude directly), then write the bot logic:

import { Client, GatewayIntentBits } from "discord.js";
import "dotenv/config";

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

client.on("messageCreate", async (message) => {
  if (message.author.bot) return;
  if (!message.mentions.has(client.user)) return;

  const prompt = message.content.replace(`<@${client.user.id}>`, "").trim();
  await message.channel.sendTyping();

  const res = await fetch("https://api.subtoapi.app/v1/messages", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SUBTOAPI_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "claude-sonnet-4-5",
      max_tokens: 400,
      messages: [{ role: "user", content: prompt }],
    }),
  });

  const data = await res.json();
  const reply = data.content?.[0]?.text ?? "Sorry, I couldn't generate a reply.";
  await message.reply(reply.slice(0, 2000)); // Discord's message length limit
});

client.login(process.env.DISCORD_TOKEN);

That's a functional bot: mention it in any channel and it replies using Claude. Discord caps messages at 2000 characters, so trim or split long responses if your use case involves detailed answers.

Step 4: Add streaming for faster perceived response

A non-streamed request means the bot sits silent until the full answer is generated, which feels slow for longer replies. You can stream tokens and edit the Discord message as they arrive instead of waiting for the full completion — details on setting up server-sent events are in /docs/streaming. In practice, a common pattern is to buffer tokens and update the Discord message every 400–600ms rather than on every token, since Discord rate-limits message edits.

Step 5: Give it memory

Claude doesn't retain conversation state between requests — each call is stateless. To make the bot remember earlier messages in a thread, keep a short in-memory map keyed by channel ID (or a Redis store if the bot runs across multiple processes) and include the last few exchanges in the messages array on each call:

const history = conversations.get(message.channelId) ?? [];
history.push({ role: "user", content: prompt });

// send history.slice(-10) as the messages array, then push the assistant reply back in

Trim history aggressively — Discord conversations are casual and don't need a 50-message context window eating into your token budget.

Step 6: Add tool use for real actions

If you want the bot to do things beyond chatting — look up a user's server stats, fetch weather, query a database — Claude's tool-calling lets the model decide when to invoke a function you define, and your code executes it and returns the result. This is the same tool-use flow documented at /docs/tools, and it works identically whether the bot is answering questions or acting as a lightweight ops assistant for your server.

Deployment notes

Run the bot on a small always-on process (a €5/month VPS, Railway, or Fly.io all work fine). Watch your token usage per server if the bot is public — a usage dashboard with per-key tracking, available with a SubToAPI account, makes it easy to spot a channel burning through your budget before it becomes a billing surprise. For getting the first request working end to end, /docs/quickstart covers the minimal setup outside the Discord-specific pieces above.

Questions

Does the bot need to respond to every message or only mentions? Mentions-only is the standard pattern — it avoids the bot replying in unrelated conversations and keeps API costs predictable. You can expand this to specific channels or slash commands later.

Can I run this bot on Anthropic's API directly instead of a hosted layer? Yes — the code above works unmodified against Anthropic's endpoint with your own API key; a hosted layer like SubToAPI mainly adds usage tracking, team keys, and streaming support without extra setup.

How do I stop the bot from exceeding Discord's rate limits when editing streamed messages? Batch token updates and edit the message on an interval (every few hundred milliseconds) rather than on every chunk, and fall back to a single final edit if the stream finishes quickly.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →