How to Use discord.js Buttons and Select Menus
🔍 WiseChecker

How to Use discord.js Buttons and Select Menus

Discord bots can do more than just respond to text commands. Buttons and select menus let users interact with your bot through clickable UI elements. These components replace older reaction-based systems and provide a cleaner user experience. This article explains how to set up and use buttons and select menus with discord.js v14.

Key Takeaways: Building Interactive Discord Bot Components

  • MessageComponentInteraction class: Handles all button and select menu clicks in your bot code.
  • ActionRowBuilder: Required container that holds up to five buttons or one select menu per row.
  • ButtonBuilder and StringSelectMenuBuilder: Create the actual clickable elements with custom IDs and labels.

What Are Discord Message Components?

Message components are interactive UI elements that appear below a bot’s message in a Discord channel. Buttons are clickable boxes that trigger an action. Select menus are dropdown lists where users pick one or more options. Both require a custom ID string that your bot uses to identify which component was clicked.

Before writing code, you need a working discord.js v14 bot. Your bot must have the Send Messages and Use External Emojis permissions in the server. You also need Node.js 16.9.0 or newer installed on your machine. The examples in this article use the discord.js package version 14.

Prerequisites for Using Components

Install discord.js v14 in your project folder:

npm install discord.js@14

Create a basic bot file with the Gateway Intent GatewayIntentBits.Guilds and GatewayIntentBits.MessageContent. You also need the partial Partials.Message and Partials.Channel if your bot handles DMs.

Creating and Sending a Button

Buttons are built with ButtonBuilder and placed inside an ActionRowBuilder. Each button needs a custom ID, a label, and a style. The style controls the button color: Primary is blue, Secondary is gray, Success is green, Danger is red, and Link opens a URL.

  1. Import the required builders
    Add these imports at the top of your file: const { ButtonBuilder, ButtonStyle, ActionRowBuilder } = require('discord.js');
  2. Create a button instance
    Use new ButtonBuilder() and chain the methods .setCustomId('my_button'), .setLabel('Click Me'), and .setStyle(ButtonStyle.Primary).
  3. Wrap the button in an action row
    Create a new ActionRowBuilder and add the button with .addComponents(button).
  4. Send the message with the component
    Use interaction.reply({ components: [row] }) or channel.send({ components: [row] }) to display the button.

Example code for a slash command that sends a button:

const { SlashCommandBuilder, ButtonBuilder, ButtonStyle, ActionRowBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('button')
    .setDescription('Sends a test button'),
  async execute(interaction) {
    const button = new ButtonBuilder()
      .setCustomId('primary_button')
      .setLabel('Click Me')
      .setStyle(ButtonStyle.Primary);

    const row = new ActionRowBuilder().addComponents(button);

    await interaction.reply({ content: 'Here is your button:', components: [row] });
  }
};

Handling Button Interactions

When a user clicks a button, Discord sends an interaction event. Your bot listens for this event using the client.on('interactionCreate') handler. Check if the interaction is a ButtonInteraction by testing interaction.isButton().

  1. Create an interaction listener
    In your main bot file, add client.on('interactionCreate', async interaction => { ... });
  2. Check the interaction type
    Inside the listener, write if (!interaction.isButton()) return; to ignore non-button interactions.
  3. Match the custom ID
    Use if (interaction.customId === 'primary_button') to run specific code for that button.
  4. Reply or update the message
    Call interaction.reply('Button clicked!') or interaction.update({ content: 'Updated message' }) to respond.

Example handler for the button above:

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

  if (interaction.customId === 'primary_button') {
    await interaction.reply({ content: 'You clicked the button!', ephemeral: true });
  }
});

Creating and Sending a Select Menu

Select menus are built with StringSelectMenuBuilder. Each option has a label, a value, and an optional description. The select menu also needs a custom ID and a placeholder text that appears before the user makes a selection.

  1. Import StringSelectMenuBuilder
    Add const { StringSelectMenuBuilder, StringSelectMenuOptionBuilder } = require('discord.js');
  2. Create options
    Use new StringSelectMenuOptionBuilder().setLabel('Option 1').setValue('option1') for each option.
  3. Build the select menu
    Create a StringSelectMenuBuilder and chain .setCustomId('menu'), .setPlaceholder('Choose an option'), and .addOptions(option1, option2).
  4. Wrap in an action row
    Create an ActionRowBuilder with .addComponents(selectMenu). An action row can hold only one select menu.
  5. Send the message
    Include the row in components when replying or sending a message.

Example slash command that sends a select menu:

const { SlashCommandBuilder, StringSelectMenuBuilder, StringSelectMenuOptionBuilder, ActionRowBuilder } = require('discord.js');

module.exports = {
  data: new SlashCommandBuilder()
    .setName('menu')
    .setDescription('Sends a select menu'),
  async execute(interaction) {
    const option1 = new StringSelectMenuOptionBuilder()
      .setLabel('Red')
      .setValue('red');
    const option2 = new StringSelectMenuOptionBuilder()
      .setLabel('Blue')
      .setValue('blue');

    const selectMenu = new StringSelectMenuBuilder()
      .setCustomId('color_menu')
      .setPlaceholder('Choose a color')
      .addOptions(option1, option2);

    const row = new ActionRowBuilder().addComponents(selectMenu);

    await interaction.reply({ content: 'Pick your favorite color:', components: [row] });
  }
};

Handling Select Menu Interactions

Select menu interactions are processed similarly to buttons. Check interaction.isStringSelectMenu() and read the selected values from interaction.values, which returns an array of strings.

  1. Add a select menu handler
    In your interactionCreate listener, add if (interaction.isStringSelectMenu()) { ... }.
  2. Get the selected value
    Use const selected = interaction.values[0]; to get the first chosen option.
  3. Respond based on the selection
    Use conditional logic to reply with different messages depending on the value.

Example handler for the color menu:

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

  if (interaction.customId === 'color_menu') {
    const selectedColor = interaction.values[0];
    await interaction.reply({ content: `You chose ${selectedColor}`, ephemeral: true });
  }
});

Common Mistakes and Limitations

Action Row Limit Exceeded

A single message can have at most five action rows. Each row can hold up to five buttons or one select menu. If you add more rows, Discord returns an error. Count your rows before sending.

Custom ID Not Unique

If two buttons or menus in the same message share the same custom ID, the bot cannot tell them apart. Always use unique custom IDs. For dynamic components, append a unique identifier like a user ID or a timestamp.

Interaction Already Acknowledged

You can reply to an interaction only once. If you need to send multiple follow-up messages, use interaction.followUp() after the initial reply. Calling interaction.reply() twice throws an error.

Select Menu Values Must Be Unique

Each option in a select menu must have a unique value string. Duplicate values cause the interaction to fail silently. Always check your option values for duplicates before sending the menu.

Buttons vs Select Menus: When to Use Each

Item Buttons Select Menus
Number of choices Up to 5 per row, 25 per message Up to 25 options per menu
User action Single click Click then select from dropdown
Best for Binary choices, confirmations, navigation Multiple options, lists, settings
Visual clarity Immediate, all options visible Compact, hidden until opened

Use buttons when you have few options and want users to act quickly. Use select menus when you have many options or need to save space in a message. You can combine both in a single message by using multiple action rows.

You now know how to create buttons and select menus with discord.js v14. Start by adding a simple button to a test command, then expand to multi-step interactions. For advanced use, try collecting multiple inputs by sending a sequence of components. Disable buttons after use by setting .setDisabled(true) on the button builder to prevent double-clicks.