Quick answer: Create a new application in the Discord Developer Portal, add a bot user, and invite it to your server with the applications.commands and bot scopes. Use Discord.

Setting up Discord bot for game bug reports correctly from the start saves you time later. Your Discord server is where your players already hang out. When they hit a bug, the first thing they do is type about it in your #general channel. The report vanishes into the chat scroll within minutes, buried under memes and build screenshots. A bug report bot fixes this. It gives players a structured form to fill out, posts the report in a dedicated channel, creates a discussion thread, and forwards the data to your bug tracker—all without anyone leaving Discord. Here is how to build one from scratch with Discord.js.

Creating the Bot in the Developer Portal

Go to the Discord Developer Portal and click "New Application." Give it a name like "BugReporter" or your game’s name followed by "Bugs." Navigate to the Bot section, click "Add Bot," and copy the bot token. Store this token securely—it is the password for your bot and should never be committed to version control or shared publicly.

Under OAuth2, select the bot and applications.commands scopes. For bot permissions, you need Send Messages, Create Public Threads, Embed Links, and Use Slash Commands. Generate the invite URL and add the bot to your server.

Project Setup

Create a new Node.js project and install the dependencies:

npm init -y
npm install discord.js dotenv

Create a .env file with your bot token, the channel ID where reports should be posted, and your bug tracker API endpoint:

DISCORD_TOKEN=your-bot-token-here
BUG_CHANNEL_ID=1234567890123456789
BUGTRACKER_API_URL=https://api.bugnet.io/v1/bugs
BUGTRACKER_API_KEY=your-api-key-here

Connecting the Bot and Registering the Slash Command

Create the main bot file. This connects to Discord, registers the /bugreport slash command, and sets up the interaction handlers:

const { Client, GatewayIntentBits, REST, Routes,
        SlashCommandBuilder, ModalBuilder, TextInputBuilder,
        TextInputStyle, ActionRowBuilder, EmbedBuilder } = require('discord.js');
require('dotenv').config();

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

// Register the /bugreport slash command
const command = new SlashCommandBuilder()
  .setName('bugreport')
  .setDescription('Submit a bug report for the game');

const rest = new REST().setToken(process.env.DISCORD_TOKEN);

client.once('ready', async () => {
  await rest.put(
    Routes.applicationCommands(client.user.id),
    { body: [command.toJSON()] }
  );
  console.log(`Bug report bot is online as ${client.user.tag}`);
});

Building the Modal Form

When a player runs /bugreport, the bot presents a modal dialog with structured fields. Modals support up to five text inputs, which is plenty for a bug report. Keep it short—players will abandon long forms:

client.on('interactionCreate', async (interaction) => {
  if (interaction.isChatInputCommand()
      && interaction.commandName === 'bugreport') {

    const modal = new ModalBuilder()
      .setCustomId('bugReportModal')
      .setTitle('Submit a Bug Report');

    const titleInput = new TextInputBuilder()
      .setCustomId('bugTitle')
      .setLabel('Bug title (short summary)')
      .setStyle(TextInputStyle.Short)
      .setRequired(true)
      .setMaxLength(100);

    const descInput = new TextInputBuilder()
      .setCustomId('bugDescription')
      .setLabel('What happened? What did you expect?')
      .setStyle(TextInputStyle.Paragraph)
      .setRequired(true)
      .setMaxLength(1000);

    const stepsInput = new TextInputBuilder()
      .setCustomId('bugSteps')
      .setLabel('Steps to reproduce')
      .setStyle(TextInputStyle.Paragraph)
      .setRequired(false)
      .setPlaceholder('1. Go to the Forest area\n2. Open inventory\n3. ...');

    const severityInput = new TextInputBuilder()
      .setCustomId('bugSeverity')
      .setLabel('Severity (low / medium / high / critical)')
      .setStyle(TextInputStyle.Short)
      .setRequired(true)
      .setPlaceholder('medium');

    modal.addComponents(
      new ActionRowBuilder().addComponents(titleInput),
      new ActionRowBuilder().addComponents(descInput),
      new ActionRowBuilder().addComponents(stepsInput),
      new ActionRowBuilder().addComponents(severityInput)
    );

    await interaction.showModal(modal);
  }
});

The modal appears as a native Discord dialog. Players fill it out without leaving their current channel, which dramatically increases completion rates compared to sending them to an external form.

Handling the Submission

When the player submits the modal, the bot extracts the field values, posts a formatted embed in your bug reports channel, and creates a thread for discussion:

client.on('interactionCreate', async (interaction) => {
  if (!interaction.isModalSubmit()) return;
  if (interaction.customId !== 'bugReportModal') return;

  const title = interaction.fields.getTextInputValue('bugTitle');
  const description = interaction.fields.getTextInputValue('bugDescription');
  const steps = interaction.fields.getTextInputValue('bugSteps') || 'Not provided';
  const severity = interaction.fields.getTextInputValue('bugSeverity');

  // Build the embed for the bug reports channel
  const severityColor = {
    critical: 0xFF0000, high: 0xFF8800,
    medium: 0xFFCC00, low: 0x44BB44
  };

  const embed = new EmbedBuilder()
    .setTitle(`Bug: ${title}`)
    .setColor(severityColor[severity.toLowerCase()] || 0x888888)
    .addFields(
      { name: 'Description', value: description },
      { name: 'Steps to Reproduce', value: steps },
      { name: 'Severity', value: severity, inline: true },
      { name: 'Reporter', value: `<@${interaction.user.id}>`, inline: true }
    )
    .setTimestamp();

  // Post in the bug reports channel
  const channel = await client.channels.fetch(process.env.BUG_CHANNEL_ID);
  const message = await channel.send({ embeds: [embed] });

  // Create a discussion thread
  const thread = await message.startThread({
    name: `Bug: ${title.substring(0, 90)}`,
    autoArchiveDuration: 1440  // Archive after 24h of inactivity
  });

  await thread.send(
    `Thanks for the report, <@${interaction.user.id}>! ` +
    `Our team will look into this. Feel free to add screenshots ` +
    `or additional details in this thread.`
  );

  // Acknowledge the submission to the reporter (ephemeral)
  await interaction.reply({
    content: `Your bug report "${title}" has been submitted. ` +
             `Follow the discussion here: ${thread.url}`,
    ephemeral: true
  });
});

Forwarding Reports to Your Bug Tracker

A Discord embed is useful for visibility, but you need the report in your actual bug tracker for triage and assignment. Add an HTTP POST after the embed is created to forward the structured data:

// Add this after creating the thread
async function forwardToBugTracker(report) {
  const response = await fetch(process.env.BUGTRACKER_API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.BUGTRACKER_API_KEY}`
    },
    body: JSON.stringify({
      title: report.title,
      description: report.description,
      steps_to_reproduce: report.steps,
      severity: report.severity,
      source: 'discord',
      reporter_discord_id: report.userId,
      discord_thread_url: report.threadUrl
    })
  });

  const data = await response.json();
  return data;
}

// Call it after thread creation
const trackerResult = await forwardToBugTracker({
  title, description, steps, severity,
  userId: interaction.user.id,
  threadUrl: thread.url
});

// Post the tracker link in the thread for reference
if (trackerResult.ok) {
  await thread.send(
    `Tracked as bug **${trackerResult.data.id}** in our bug tracker.`
  );
}

This creates a two-way link: the Discord thread references the bug tracker ID, and the bug tracker entry links back to the Discord thread. When your team resolves the bug, you can post an update in the thread to close the loop with the reporter.

Running the Bot

Add the login call at the bottom of your bot file and start it:

client.login(process.env.DISCORD_TOKEN);

For production, run the bot with a process manager like PM2 or deploy it as a small container. The bot uses minimal resources—a single instance handles thousands of reports per day without issue. Add a health check endpoint if you want to monitor uptime.

Tips for Better Reports

The modal form does the heavy lifting of structuring reports, but a few additional touches improve quality. Pin a message in your bug reports channel explaining what makes a good report. Include an example with a clear title, specific description, and numbered reproduction steps. Players who see the example produce better reports.

Consider adding a /bugstatus command that lets players check the status of their report by ID. This reduces "any update?" messages in your support channels and gives players a sense that their reports are being tracked.

If your game supports it, encourage players to include their in-game player ID in the report. This lets you pull their save data, replay logs, or crash reports from your backend and cross-reference with the Discord report for faster debugging.

"Players report bugs where they already are. If your community lives on Discord, your bug reporting should too. Meet players where they are, not where you wish they were."

Related Issues

For broader strategies on collecting player feedback through multiple channels, see our guide on how to collect player feedback in-game. If you are a solo developer looking for the right tools to manage incoming reports, read our roundup of the best bug tracking tools for solo game developers.

A 30-minute bot setup today saves you hundreds of hours of digging through chat logs for bug reports later.