A Discord AI bot is a bot that replies to messages in your server with fluent, AI-powered answers. In this guide we will build a fully working bot that captures incoming messages with discord.js, sends them to Anthropic's Claude API, and posts the response back to the channel. Unlike classic command-driven bots, the goal here is an assistant that converses in natural language and remembers context.
What you need for a Discord AI bot
Before you start, prepare a few essentials:
- Node.js 18+ — discord.js v14 and the official Anthropic SDK require a modern Node version.
- A Discord application and bot token — create an application in the
Discord Developer Portal, turn it into a bot, and copy the token. - An Anthropic API key — obtained from
console.anthropic.comand stored in a.envfile. - The Message Content Intent — you must enable this privileged intent in the Developer Portal so the bot can read message content.
Never hardcode the token or API key into your source; keep both as environment variables. If the key leaks, the abuse bill lands on you.
Setting up the project and dependencies
Initialize the project in an empty folder and install the required packages:
mkdir discord-ai-bot && cd discord-ai-bot
npm init -y
npm install discord.js @anthropic-ai/sdk dotenv
Add "type": "module" to your package.json so you can use ES module syntax (import). Then create a .env file in the root:
DISCORD_TOKEN=your_discord_bot_token
ANTHROPIC_API_KEY=your_anthropic_key
The SDK automatically reads the ANTHROPIC_API_KEY environment variable, so you don't need to pass the key by hand when creating the client.
Talking to the Claude API: the core code
The real work happens in Claude's Messages API: you pick a model and supply a messages array (role and content), and you get back a message made of content blocks. The index.js below asks Claude whenever the bot is mentioned and writes the answer back:
import 'dotenv/config';
import { Client, GatewayIntentBits, Events } from 'discord.js';
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic(); // key is read from the environment
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
const SYSTEM_PROMPT =
'You are a helpful Discord community assistant. Keep answers short and clear.';
client.once(Events.ClientReady, (c) => {
console.log(`Logged in as ${c.user.tag}`);
});
client.on(Events.MessageCreate, async (message) => {
if (message.author.bot) return;
if (!message.mentions.has(client.user)) return;
const prompt = message.content.replace(/<@!?\d+>/g, '').trim();
if (!prompt) return;
await message.channel.sendTyping();
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages: [{ role: 'user', content: prompt }],
});
const reply = response.content
.filter((block) => block.type === 'text')
.map((block) => block.text)
.join('');
await message.reply(reply.slice(0, 2000));
});
client.login(process.env.DISCORD_TOKEN);
A few details to note: Claude's reply lives in response.content as an array of blocks, so to get the text we filter the text-type blocks and join them. We slice the reply because Discord's message limit is 2000 characters. The system field is the system prompt that defines the bot's personality and language.
Conversation memory and context management
The bot above handles every message from scratch; it doesn't remember the previous conversation. For a real chat, you store the recent messages per channel and send the history back on every request. The Claude API is stateless: you carry the context.
const history = new Map();
// inside MessageCreate:
const channelId = message.channelId;
const messages = history.get(channelId) ?? [];
messages.push({ role: 'user', content: prompt });
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-6',
max_tokens: 1024,
system: SYSTEM_PROMPT,
messages,
});
const reply = response.content
.filter((block) => block.type === 'text')
.map((block) => block.text)
.join('');
messages.push({ role: 'assistant', content: reply });
// Limit context to the last 10 messages (token and cost control)
history.set(channelId, messages.slice(-10));
An important rule here: messages alternate between the user and assistant roles, and the first message must always be user. If you let the history grow without bound, token cost rises and you fill the context window, so trimming to the last N messages is good practice. For long-term persistence, store this array in a database (SQLite, for example).
Cost, speed, and security tips
- Error handling: wrap the API call in
try/catch; on a network error or rate limit, return a polite message to the user and log the error. - Cooldown: add a simple per-user wait time to block both spam and needless API cost.
- Model choice: for high-volume, speed-sensitive workloads the Sonnet family is a balanced pick; consider Opus for more complex tasks and Haiku for simple, fast replies.
- max_tokens: set a reasonable upper bound to keep replies short — a long answer is both costly and may exceed Discord's limit.
- System prompt: define the bot's role, boundaries, and tone clearly in the
systemfield; this is the most effective way to steer model behavior.
Put these pieces together and you have a solid assistant that talks in natural language, remembers context, and keeps cost under control.
Frequently Asked Questions
My bot can't read messages — why?
Most likely the Message Content Intent is off. Enable this privileged intent in the bot settings in the Developer Portal and make sure you added the GatewayIntentBits.MessageContent intent in your code.
Which Claude model should I use?
For chat bots a current model like claude-sonnet-4-6 offers a good balance of speed and quality. You can switch to a more powerful or faster model as needed; just change the model ID in the model field of the request.
How do I keep my API key safe?
Keep the key in a .env file, add it to .gitignore, and never push it to your repo. If the key leaks by accident, revoke it from the console immediately and create a new one.
Want an AI-powered bot for your Discord community? Whether it's a build from scratch or adding Claude to your existing bot, let's plan it together. Get in touch with me and let's bring your project to life.