Use Cases

Discord Bot Development with Claude Code

Learn about Discord bot development using Claude Code. Practical tips and code examples included.

Streamlining Discord Bot Development With Claude Code

Discord bots are used across a wide range of use cases: running communities, managing game servers, and distributing notifications. The discord.js library makes it easy to develop in TypeScript, but interaction handling and permission management still require a lot of code. Let’s leverage Claude Code to efficiently build a feature-rich Discord bot.

Project Setup

> Create a Discord bot project.
> Use discord.js v14, TypeScript, and a command handler structure.
npm init -y
npm install discord.js
npm install -D typescript @types/node tsx
// src/index.ts
import { Client, GatewayIntentBits, Collection, Events } from 'discord.js';
import { registerCommands } from './commands';

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

client.commands = new Collection();

client.once(Events.ClientReady, (c) => {
  console.log(`Logged in as ${c.user.tag}`);
});

// Command handler
client.on(Events.InteractionCreate, async (interaction) => {
  if (!interaction.isChatInputCommand()) return;

  const command = client.commands.get(interaction.commandName);
  if (!command) return;

  try {
    await command.execute(interaction);
  } catch (error) {
    console.error(error);
    const reply = {
      content: 'An error occurred while running the command.',
      ephemeral: true,
    };
    if (interaction.replied || interaction.deferred) {
      await interaction.followUp(reply);
    } else {
      await interaction.reply(reply);
    }
  }
});

registerCommands(client);
client.login(process.env.DISCORD_TOKEN);

Implementing Slash Commands

> Implement a voting feature using a /poll command.
> Allow voting via buttons.
// src/commands/poll.ts
import {
  SlashCommandBuilder,
  ActionRowBuilder,
  ButtonBuilder,
  ButtonStyle,
  EmbedBuilder,
  ChatInputCommandInteraction,
  ButtonInteraction,
} from 'discord.js';

const polls = new Map<string, { question: string; votes: Map<string, Set<string>> }>();

export const data = new SlashCommandBuilder()
  .setName('poll')
  .setDescription('Create a poll')
  .addStringOption((opt) =>
    opt.setName('question').setDescription('Question').setRequired(true)
  )
  .addStringOption((opt) =>
    opt.setName('option1').setDescription('Option 1').setRequired(true)
  )
  .addStringOption((opt) =>
    opt.setName('option2').setDescription('Option 2').setRequired(true)
  )
  .addStringOption((opt) =>
    opt.setName('option3').setDescription('Option 3').setRequired(false)
  );

export async function execute(interaction: ChatInputCommandInteraction) {
  const question = interaction.options.getString('question', true);
  const options = [
    interaction.options.getString('option1', true),
    interaction.options.getString('option2', true),
    interaction.options.getString('option3'),
  ].filter(Boolean) as string[];

  const pollId = `poll-${Date.now()}`;
  const votes = new Map<string, Set<string>>();
  options.forEach((opt) => votes.set(opt, new Set()));
  polls.set(pollId, { question, votes });

  const embed = new EmbedBuilder()
    .setTitle('📊 ' + question)
    .setColor(0x6366f1)
    .setDescription(
      options.map((opt) => `**${opt}**: 0 votes`).join('\n')
    )
    .setFooter({ text: `Poll ID: ${pollId}` });

  const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
    ...options.map((opt, i) =>
      new ButtonBuilder()
        .setCustomId(`${pollId}:${opt}`)
        .setLabel(opt)
        .setStyle(ButtonStyle.Primary)
    )
  );

  await interaction.reply({ embeds: [embed], components: [row] });
}

// Button handler
export async function handlePollButton(interaction: ButtonInteraction) {
  const [pollId, option] = interaction.customId.split(':');
  const poll = polls.get(pollId);
  if (!poll) return;

  const userId = interaction.user.id;

  // Remove existing votes by this user
  for (const voters of poll.votes.values()) {
    voters.delete(userId);
  }

  // Add the new vote
  poll.votes.get(option)?.add(userId);

  const description = Array.from(poll.votes.entries())
    .map(([opt, voters]) => {
      const count = voters.size;
      const bar = '█'.repeat(count) || '░';
      return `**${opt}**: ${count} votes ${bar}`;
    })
    .join('\n');

  const embed = new EmbedBuilder()
    .setTitle('📊 ' + poll.question)
    .setColor(0x6366f1)
    .setDescription(description)
    .setFooter({ text: `Poll ID: ${pollId}` });

  await interaction.update({ embeds: [embed] });
}

Embed Messages

> Create a command that displays server information.
> Display it as a rich Embed.
// src/commands/serverinfo.ts
import {
  SlashCommandBuilder,
  EmbedBuilder,
  ChatInputCommandInteraction,
} from 'discord.js';

export const data = new SlashCommandBuilder()
  .setName('serverinfo')
  .setDescription('Display server information');

export async function execute(interaction: ChatInputCommandInteraction) {
  const guild = interaction.guild;
  if (!guild) return;

  const embed = new EmbedBuilder()
    .setTitle(guild.name)
    .setThumbnail(guild.iconURL() || '')
    .setColor(0x6366f1)
    .addFields(
      { name: 'Members', value: `${guild.memberCount}`, inline: true },
      { name: 'Channels', value: `${guild.channels.cache.size}`, inline: true },
      { name: 'Roles', value: `${guild.roles.cache.size}`, inline: true },
      { name: 'Created', value: guild.createdAt.toLocaleDateString('en-US'), inline: true },
      { name: 'Boost', value: `Lv.${guild.premiumTier} (${guild.premiumSubscriptionCount})`, inline: true },
    )
    .setTimestamp();

  await interaction.reply({ embeds: [embed] });
}

Registering Commands

// src/commands/index.ts
import { REST, Routes, Client } from 'discord.js';
import * as poll from './poll';
import * as serverinfo from './serverinfo';

const commands = [poll, serverinfo];

export function registerCommands(client: Client) {
  for (const command of commands) {
    client.commands.set(command.data.name, command);
  }
}

// Register slash commands with Discord
export async function deployCommands() {
  const rest = new REST().setToken(process.env.DISCORD_TOKEN!);
  const body = commands.map((c) => c.data.toJSON());

  await rest.put(
    Routes.applicationCommands(process.env.DISCORD_CLIENT_ID!),
    { body }
  );
  console.log('Commands deployed');
}

Summary

Combining discord.js with Claude Code lets you efficiently build an interactive Discord bot. See the chatbot development guide and the API development basics for related topics.

For more on discord.js, see the official discord.js guide.

#Claude Code #Discord Bot #discord.js #chatbot #community

Level up your Claude Code workflow

50 battle-tested prompt templates you can copy-paste into Claude Code right now.

Free

Free PDF: Claude Code Cheatsheet in 5 Minutes

Key commands, shortcuts, and prompt examples on a single printable page.

Download PDF
M

About the Author

Masa

Engineer obsessed with Claude Code. Runs claudecode-lab.com, a 10-language tech media with 2,000+ pages.