Problem: Your Team Needs AI Help in Discord
You want to add Claude's intelligence to your Discord server for answering questions, summarizing conversations, or automating workflows, but you're not sure how to connect the APIs.
You'll learn:
- Set up Discord.js v14 with Node.js 24
- Integrate Claude 4.5 Sonnet API
- Handle slash commands and message replies
- Deploy with proper error handling
Time: 20 min | Level: Intermediate
Why This Approach Works
Discord bots need both real-time message handling (Discord.js) and intelligent responses (Claude API). This guide uses Claude 4.5 Sonnet for fast, cost-effective responses suitable for team chat.
Common symptoms you're solving:
- Team asks repetitive questions that could be answered by AI
- Need code review or debugging help in Discord
- Want to automate meeting summaries or documentation searches
Prerequisites
# Check versions
node --version # Should be v24.x
npm --version # Should be v10.x
You'll need:
- Discord account with server admin access
- Anthropic API key (get one here)
- 20 minutes and a code editor
Solution
Step 1: Create Discord Application
- Go to Discord Developer Portal
- Click "New Application" → Name it "Team Claude"
- Navigate to "Bot" tab → Click "Add Bot"
- Under "Privileged Gateway Intents" enable:
- Message Content Intent
- Server Members Intent
- Click "Reset Token" → Copy the token (you'll need this)
Expected: You now have a bot token starting with MTI...
If it fails:
- "Missing Access": You need admin permissions on your Discord server
- Can't enable intents: Some bots created before 2022 can't enable these - create a new application
Step 2: Set Up Node.js Project
mkdir discord-claude-bot
cd discord-claude-bot
npm init -y
# Install dependencies
npm install discord.js@14.14.1 @anthropic-ai/sdk@0.30.0 dotenv@16.4.1
Create .env file:
# .env
DISCORD_TOKEN=your_discord_bot_token_here
ANTHROPIC_API_KEY=your_anthropic_key_here
Why this works: Environment variables keep secrets out of code. The specific package versions are tested together.
Step 3: Build the Bot
Create bot.js:
// bot.js
import { Client, GatewayIntentBits, REST, Routes } from 'discord.js';
import Anthropic from '@anthropic-ai/sdk';
import 'dotenv/config';
// Initialize clients
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
]
});
const anthropic = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
// Bot ready event
client.once('ready', () => {
console.log(`✅ Bot logged in as ${client.user.tag}`);
});
// Handle slash commands
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ask') {
await interaction.deferReply(); // Shows "thinking..." to users
const question = interaction.options.getString('question');
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [{
role: 'user',
content: question
}]
});
// Extract text from Claude's response
const reply = message.content[0].text;
// Discord has 2000 char limit - truncate if needed
const truncated = reply.length > 2000
? reply.substring(0, 1997) + '...'
: reply;
await interaction.editReply(truncated);
} catch (error) {
console.error('Claude API error:', error);
await interaction.editReply('❌ Sorry, I encountered an error. Please try again.');
}
}
});
// Login
client.login(process.env.DISCORD_TOKEN);
Why this structure:
deferReply()prevents timeout on slow API calls- Error handling prevents bot crashes
- Truncation handles Discord's message limits
Step 4: Register Slash Commands
Create register-commands.js:
// register-commands.js
import { REST, Routes } from 'discord.js';
import 'dotenv/config';
const commands = [
{
name: 'ask',
description: 'Ask Claude a question',
options: [
{
name: 'question',
description: 'Your question for Claude',
type: 3, // STRING type
required: true,
},
],
},
];
const rest = new REST({ version: '10' }).setToken(process.env.DISCORD_TOKEN);
(async () => {
try {
console.log('Registering slash commands...');
// Get application ID from your bot token
const clientId = Buffer.from(
process.env.DISCORD_TOKEN.split('.')[0],
'base64'
).toString('ascii');
await rest.put(
Routes.applicationCommands(clientId),
{ body: commands },
);
console.log('✅ Commands registered globally');
} catch (error) {
console.error('Error:', error);
}
})();
Run command registration:
node register-commands.js
Expected output:
Registering slash commands...
✅ Commands registered globally
If it fails:
- 401 Unauthorized: Check your DISCORD_TOKEN in
.env - Commands don't appear: Wait 1 hour for global commands, or use guild-specific commands for instant updates
Step 5: Start Your Bot
node bot.js
Expected output:
✅ Bot logged in as Team Claude#1234
Invite bot to your server:
- Developer Portal → Your App → OAuth2 → URL Generator
- Select scopes:
bot,applications.commands - Select permissions:
Send Messages,Use Slash Commands - Copy generated URL and open in browser
- Select your server → Authorize
Verification
Test in Discord:
- Type
/ask question: What is Docker? - Bot shows "thinking..."
- Claude responds with explanation
You should see: A coherent answer within 2-5 seconds.
Common issues:
- Bot offline: Check
node bot.jsis running - "This interaction failed": Check console for errors, verify API key
- Slow responses: Claude can take 3-5s for complex queries - this is normal
Production Improvements
Add Conversation Context
// Store last 5 messages per user
const conversationHistory = new Map();
client.on('interactionCreate', async (interaction) => {
if (interaction.commandName === 'ask') {
await interaction.deferReply();
const userId = interaction.user.id;
const question = interaction.options.getString('question');
// Get or create user history
if (!conversationHistory.has(userId)) {
conversationHistory.set(userId, []);
}
const history = conversationHistory.get(userId);
try {
const message = await anthropic.messages.create({
model: 'claude-sonnet-4-5-20250929',
max_tokens: 1000,
messages: [
...history, // Previous messages
{ role: 'user', content: question }
]
});
const reply = message.content[0].text;
// Update history (keep last 5 exchanges)
history.push({ role: 'user', content: question });
history.push({ role: 'assistant', content: reply });
if (history.length > 10) history.splice(0, 2);
await interaction.editReply(reply.substring(0, 2000));
} catch (error) {
console.error(error);
await interaction.editReply('❌ Error occurred');
}
}
});
Why this works: Claude uses conversation history to give contextual answers. Limiting to 5 exchanges prevents token costs from growing.
Add Rate Limiting
import { RateLimiter } from 'limiter';
// 10 requests per minute per user
const limiter = new RateLimiter({ tokensPerInterval: 10, interval: 'minute' });
client.on('interactionCreate', async (interaction) => {
if (interaction.commandName === 'ask') {
const userId = interaction.user.id;
// Check rate limit
const canProceed = await limiter.removeTokens(1);
if (!canProceed) {
await interaction.reply({
content: '⏱️ Slow down! Try again in a minute.',
ephemeral: true // Only visible to user
});
return;
}
await interaction.deferReply();
// ... rest of code
}
});
Install limiter:
npm install limiter@2.1.0
Deployment Options
Option 1: PM2 (Simple VPS)
npm install -g pm2
pm2 start bot.js --name discord-claude
pm2 save
pm2 startup # Follow instructions to run on reboot
Option 2: Docker
Create Dockerfile:
FROM node:24-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
CMD ["node", "bot.js"]
Run:
docker build -t discord-claude-bot .
docker run -d --env-file .env --restart unless-stopped discord-claude-bot
Option 3: Railway/Render
- Push code to GitHub
- Connect to Railway/Render
- Add environment variables in dashboard
- Deploy automatically on push
What You Learned
- Discord.js v14 handles real-time messaging and slash commands
- Claude API integration uses simple message format
deferReply()prevents timeouts on slow API calls- Conversation history enables contextual responses
Limitations:
- Claude API costs $3 per million input tokens - monitor usage
- Discord 2000 char limit requires truncation
- No built-in moderation - add filters for production
Cost estimate: ~$0.50/month for a 10-person team with moderate usage.
Troubleshooting
Bot disconnects randomly:
// Add auto-reconnect
client.on('error', error => {
console.error('Discord client error:', error);
});
process.on('unhandledRejection', error => {
console.error('Unhandled promise rejection:', error);
});
Claude responses are cut off:
- Increase
max_tokensto 2000 (costs more) - Or implement pagination with Discord buttons for long responses
High API costs:
- Cache common questions using a Map or Redis
- Use Claude Haiku for simple queries, Sonnet for complex ones
- Add 1-hour cooldown per user per question
Tested on Node.js 24.0.0, Discord.js 14.14.1, Claude API 2026-02-01, Ubuntu 22.04