Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Specialized skill for building production-ready Discord bots. Covers Discord.js (JavaScript) and Pycord (Python), gateway intents, slash commands, interactive components, rate limiting, and sharding.
.claude/skills/davila7-discord-bot-architect/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 117% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 133% | 0% |
| case-13 | ✓→✗ | ▼ Worse | 138% | 0% |
| case-11 | ✓→✓ | = Same ✓ | 80% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 61% | 0% |
Modern Discord bot setup with Discord.js v14 and slash commands
When to use: 'Building Discord bots with JavaScript/TypeScript', 'Need full gateway connection with events', 'Building bots with complex interactions']
javascript
// src/index.js const { Client, Collection, GatewayIntentBits, Events } = require('discord.js'); const fs = require('node:fs'); const path = require('node:path'); require('dotenv').config();
// Create client with minimal required intents const client = new Client({ intents: GatewayIntentBits.Guilds, // Add only what you need: // GatewayIntentBits.GuildMessages, // GatewayIntentBits.MessageContent, // PRIVILEGED - avoid if possible ] });
// Load commands client.commands = new Collection(); const commandsPath = path.join(__dirname, 'commands'); const commandFiles = fs.readdirSync(commandsPath).filter(f => f.endsWith('.js'));
for (const file of commandFiles) { const filePath = path.join(commandsPath, file); const command = require(filePath); if ('data' in command && 'execute' in command) { client.commands.set(command.data.name, command); } }
// Load events const eventsPath = path.join(__dirname, 'events'); const eventFiles = fs.readdirSync(eventsPath).filter(f => f.endsWith('.js'));
for (const file of eventFiles) { const filePath = path.join(eventsPath, file); const event = require(filePath); if (event.once) { client.once(event.name, (...args) => event.execute(...args)); } else { client.on(event.name, (...args) => event.execute(...args)); } }
client.login(process.env.DISCORD_TOKEN);
// src/commands/ping.js const { SlashCommandBuilder } = require('discord.js');
module.exports = { data: new SlashCommandBuilder() .setName('ping') .setDescription('Replies with Pong!'),
async execute(interaction) { const sent = await interaction.reply({ content: 'Pinging...', fetchReply: true });
const latency = sent.createdTimestamp - interaction.createdTimestamp; await interaction.editReply(Pong! Latency: ${latency}ms); } };
// src/events/interactionCreate.js const { Events } = require('discord.js');
module.exports = { name: Event
### Pycord Bot Foundation
Discord bot with Pycord (Python) and application commands
**When to use**: ['Building Discord bots with Python', 'Prefer async/await patterns', 'Need good slash command support']
python# main.py import os import discord from discord.ext import commands from dotenv import load_dotenv load_dotenv() # Configure intents - only enable what you need intents = discord.Intents.default() # intents.message_content = True # PRIVILEGED - avoid if possible # intents.members = True # PRIVILEGED bot = commands.Bot( command_prefix="!", # Legacy, prefer slash commands intents=intents ) @bot.event async def on_ready(): print(f"Logged in as {bot.user}") # Sync commands (do this carefully - see sharp edges) # await bot.sync_commands() # Slash command @bot.slash_command(name="ping", description="Check bot latency") async def ping(ctx: discord.ApplicationContext): latency = round(bot.latency * 1000) await ctx.respond(f"Pong! Latency: {latency}ms") # Slash command with options @bot.slash_command(name="greet", description="Greet a user") async def greet( ctx: discord.ApplicationContext, user: discord.Option(discord.Member, "User to greet"), message: discord.Option(str, "Custom message", required=False) ): msg = message or "Hello!" await ctx.respond(f"{user.mention}, {msg}") # Load cogs for filename in os.listdir("./cogs"): if filename.endswith(".py"): bot.load_extension(f"cogs.{filename[:-3]}") bot.run(os.environ["DISCORD_TOKEN"])
python# cogs/general.py import discord from discord.ext import commands class General(commands.Cog): def __init__(self, bot): self.bot = bot @commands.slash_command(name="info", description="Bot information") async def info(self, ctx: discord.ApplicationContext): embed = discord.Embed( title="Bot Info", description="A helpful Discord bot", color=discord.Color.blue() ) embed.add_field(name="Servers", value=len(self.bot.guilds)) embed.add_field(name="Latency", value=f"{round(self.bot.latency * 1000)}ms") await ctx.respond(embed=embed) @commands.Cog.
Using buttons, select menus, and modals for rich UX
When to use: 'Need interactive user interfaces', 'Collecting user input beyond slash command options', 'Building menus, confirmations, or forms']
python
// Discord.js - Buttons and Select Menus const { SlashCommandBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle, StringSelectMenuBuilder, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
module.exports = { data: new SlashCommandBuilder() .setName('menu') .setDescription('Shows an interactive menu'),
async execute(interaction) { // Button row const buttonRow = new ActionRowBuilder() .addComponents( new ButtonBuilder() .setCustomId('confirm') .setLabel('Confirm') .setStyle(ButtonStyle.Primary), new ButtonBuilder() .setCustomId('cancel') .setLabel('Cancel') .setStyle(ButtonStyle.Danger), new ButtonBuilder() .setLabel('Documentation') .setURL('https://discord.js.org') .setStyle(ButtonStyle.Link) // Link buttons don't emit events );
// Select menu row (one per row, takes all 5 slots) const selectRow = new ActionRowBuilder() .addComponents( new StringSelectMenuBuilder() .setCustomId('select-role') .setPlaceholder('Select a role') .setMinValues(1) .setMaxValues(3) .addOptions( { label: 'Developer', value: 'dev', emoji: '💻' }, { label: 'Designer', value: 'design', emoji: '🎨' }, { label: 'Community', value: 'community', emoji: '🎉' } ]) );
await interaction.reply({ content: 'Choose an option:', components: buttonRow, selectRow] });
// Collect responses const collector = interaction.channel.createMessageComponentCollector({ filter: i => i.user.id === interaction.user.id, time: 60_000 // 60 seconds timeout });
collector.on('collect', async i => { if (i.customId === 'confirm') { await i.update({ content: 'Confirmed!', components: ] }); collector.stop(); } else if (i.custo
## Anti-Patterns
### ❌ Message Content for Commands
**Why bad**: Message Content Intent is privileged and deprecated for bot commands.
Slash commands are the intended approach.
### ❌ Syncing Commands on Every Start
**Why bad**: Command registration is rate limited. Global commands take up to 1 hour
to propagate. Syncing on every start wastes API calls and can hit limits.
### ❌ Blocking the Event Loop
**Why bad**: Discord gateway requires regular heartbeats. Blocking operations
cause missed heartbeats and disconnections.
## ⚠️ Sharp Edges
| Issue | Severity | Solution |
|-------|----------|----------|
| Issue | critical | ## Acknowledge immediately, process later |
| Issue | critical | ## Step 1: Enable in Developer Portal |
| Issue | high | ## Use a separate deploy script (not on startup) |
| Issue | critical | ## Never hardcode tokens |
| Issue | high | ## Generate correct invite URL |
| Issue | medium | ## Development: Use guild commands |
| Issue | medium | ## Never block the event loop |
| Issue | medium | ## Show modal immediately || Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-11 | pass→pass | 13,717 | 12,930 | -6% | 1 | 1 | 0% | 2,409 | 4,325 | +80% | 0 | 0 | — |
case-01 | pass→pass | 15,290 | 13,460 | -12% | 1 | 1 | 0% | 3,050 | 4,901 | +61% | 0 | 0 | — |
case-02 | fail→pass | 9,430 | 8,080 | -14% | 1 | 1 | 0% | 1,581 | 3,423 | +117% | 0 | 0 | — |
case-03 | pass→pass | 6,311 | 5,166 | -18% | 1 | 1 | 0% | 1,231 | 3,117 | +153% | 0 | 0 | — |
case-04 | pass→pass | 5,999 | 6,233 | +4% | 1 | 1 | 0% | 1,163 | 3,342 | +187% | 0 | 0 | — |
case-09 | pass→pass | 6,517 | 7,374 | +13% | 1 | 1 | 0% | 1,290 | 3,590 | +178% | 0 | 0 | — |
case-05 | fail→pass | 7,263 | 7,461 | +3% | 1 | 1 | 0% | 1,548 | 3,601 | +133% | 0 | 0 | — |
case-06 | pass→pass | 15,079 | 14,457 | -4% | 1 | 1 | 0% | 2,646 | 4,765 | +80% | 0 | 0 | — |
case-07 | pass→pass | 9,821 | 10,755 | +10% | 1 | 1 | 0% | 1,887 | 4,238 | +125% | 0 | 0 | — |
case-08 | pass→pass | 5,260 | 5,141 | -2% | 1 | 1 | 0% | 956 | 3,104 | +225% | 0 | 0 | — |
case-10 | pass→pass | 11,315 | 10,204 | -10% | 1 | 1 | 0% | 2,186 | 4,119 | +88% | 0 | 0 | — |
case-12 | pass→pass | 7,117 | 7,615 | +7% | 1 | 1 | 0% | 1,347 | 3,622 | +169% | 0 | 0 | — |
case-13 | pass→fail | 7,474 | 9,406 | +26% | 1 | 1 | 0% | 1,588 | 3,782 | +138% | 0 | 0 | — |
case-14 | pass→pass | 7,687 | 8,819 | +15% | 1 | 1 | 0% | 1,657 | 3,870 | +134% | 0 | 0 | — |
case-15 | pass→pass | 9,638 | 9,846 | +2% | 1 | 1 | 0% | 1,862 | 3,815 | +105% | 0 | 0 | — |
case-16 | pass→pass | 7,093 | 7,181 | +1% | 1 | 1 | 0% | 1,537 | 3,499 | +128% | 0 | 0 | — |
case-17 | pass→pass | 6,651 | 5,449 | -18% | 1 | 1 | 0% | 1,254 | 3,170 | +153% | 0 | 0 | — |
case-18 | pass→pass | 11,994 | 13,188 | +10% | 1 | 1 | 0% | 2,277 | 4,455 | +96% | 0 | 0 | — |
case-19 | pass→pass | 10,328 | 9,992 | -3% | 1 | 1 | 0% | 1,655 | 3,749 | +127% | 0 | 0 | — |
case-20 | pass→pass | 9,036 | 6,410 | -29% | 1 | 1 | 0% | 1,690 | 3,453 | +104% | 0 | 0 | — |
case-21 | pass→pass | 6,293 | 5,706 | -9% | 1 | 1 | 0% | 1,144 | 3,132 | +174% | 0 | 0 | — |
case-22 | pass→pass | 12,804 | 13,871 | +8% | 1 | 1 | 0% | 2,102 | 4,270 | +103% | 0 | 0 | — |
case-23 | pass→pass | 8,140 | 9,503 | +17% | 1 | 1 | 0% | 1,639 | 3,792 | +131% | 0 | 0 | — |
case-24 | pass→pass | 12,629 | 10,225 | -19% | 1 | 1 | 0% | 2,726 | 4,322 | +59% | 0 | 0 | — |
case-25 | pass→pass | 8,406 | 11,261 | +34% | 1 | 1 | 0% | 1,785 | 4,275 | +139% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.