Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save DarkStoorM/7491224767bab6fd03879a3846824d81 to your computer and use it in GitHub Desktop.
Save DarkStoorM/7491224767bab6fd03879a3846824d81 to your computer and use it in GitHub Desktop.

message components

Discord Message Components

This gist was written with discord.js@v13 and it covers only the interaction response (SlashCommands) and this is just a tiny portion of what Discord offers with message components. Head over to Discord's Guide for more details. This is a rough and a quick draft, might contain errors ^^

Discord API allows bots to send a message with additional message components, like: Select Menu and Buttons which can execute other commands defined in the bot's codebase.

With a little help of Discord Command and Component Builders, they are easy in implementation and handling.

Message Component Structure

To create a message with both SelectMenu and a couple Buttons, the message components need to be divided into two ActionRows. The reason for this is because Discord allows only one SelectMenu per row, which occupies an entire row and it can not be inserted with other components. That means we can have 5 select menus at most in one message.

One ActionRow can also fit 5 buttons.

The structure has to be defined as following:

  • Row1: Select Menu
  • Row2: 2 Buttons

The following structure can be defined by the following:

import { Embed } from "@discordjs/builders";
import {
  Client,
  CommandInteraction,
  Interaction,
  MessageActionRow,
  MessageButton,
  MessageSelectMenu,
} from "discord.js";

const client = new Client({
  intents: [
    /* your intents here */
  ],
});

client.login(/* your token here */);

// The following is a response to previously executed Slash Command
client.on("interactionCreate", async (interaction: Interaction): Promise<void> => {
  const messageContents =
    "Discord Bot message containing 2 embeds, a `SelectMenu` with 2 options and some buttons";

  const selectMenu = new MessageSelectMenu()
    .setCustomId("SomeSelectMenu")
    .setPlaceholder("--- choose your option ---")
    .addOptions([
      { label: "Select option 1", value: "one" },
      { label: "Select option 2", value: "two" },
    ]);

  const button1 = new MessageButton()
    .setCustomId("button1")
    .setLabel("First Button")
    .setStyle("SUCCESS");

  const button2 = new MessageButton()
    .setCustomId("button2")
    .setLabel("Second Button")
    .setStyle("DANGER");

  const embed1 = new Embed().setDescription("Message Embed 1");
  const embed2 = new Embed()
    .setDescription("Message Embed 2")
    .setFooter({ text: "embed footer" })
    .setTimestamp(new Date());

  // Define an array of message components (rows containing components)
  const messageComponents: MessageActionRow[] = [
    /* --- Action Row 1 --- */
    new MessageActionRow().addComponents([selectMenu]),

    /* --- Action Row 2 --- */
    new MessageActionRow().addComponents([button1, button2]),
  ];

  // Just assume this interaction came from SlashCommand
  await (<CommandInteraction>interaction).reply({
    content: messageContents,
    components: messageComponents,
    embeds: [embed1, embed2],
  });
});

The above structure results in the following response:

message

If we were to break this structure down, we can see this response allows us to execute 4 commands on the server side: two select options and two buttons.

Selecting an option from the SelectMenu will cause the client to catch that interaction, which tells us that we have received a SelectMenu object.

Within this object, we also receive an array of values, which can be accessed via:

// Index can always be 0 if we are not using a multi-select menu
interaction.values[index];

// From a single-selection menu, it does not matter what user chooses from the list, 
// values are pushed into the array, so from the above SelectMenu we can receive:
interaction.values[0] // "one"
interaction.values[0] // or "two"

Now we can handle our values

// Assuming the User selected Option 1
client.on("interactionCreate", async (interaction: Interaction): Promise<void> => {
  if (interaction.isSelectMenu()) {
    const commandName = interaction.customId;

    if (commandName === "SomeSelectMenu") {
      const value = interaction.values[0];
      
      /* 
       * Depending on the implementation, the bot can execute 'SomeSelectMenu' command internally
       * and pass the selected value or even execute another command, if the select menu option
       * contains a bot command name
       */
    }
  }
}

Very similar approach can be applied to the buttons:

// Assuming the User clicked Button 2
client.on("interactionCreate", async (interaction: Interaction): Promise<void> => {
  if (interaction.isButton()) {
    const commandName = interaction.customId;

    if (commandName === "button2") {
      /* 
       * We have a couple available options here.
       * - treat commandName as a new command we could execute
       * - instead, treat it as a Response Value and do something with it
       */
       
       // If we were to use the button's customId as a value, we could instead look
       // up the interaction this button came from
       interaction.message.interaction.id; // <- interaction that displayed this button
       // ^ with this, we can link this button's value to another command,
       // for example, we have a question with four buttons as answers
    }
  }
}

Receiving Message Component Data

Assume the following scenario:

  • user executes some Slash Command on Discord
  • client catches the interaction emit
  • user sees a SelectMenu in response to his command and selects one of the options
  • client catches another interaction emit
  • the interaction is now of SelectMenu type

By selecting a menu option from this structure's SelectMenu, the client's event listener will receive a new Interaction, a select menu object, which then can be parsed by the bot:

client.on("interactionCreate", async (interaction: Interaction): Promise<void> => {
  if (interaction.isSelectMenu()) {
    // The following is now your new command name
    const commandName = interaction.customId;

    // The commandName can now be used by the bot to execute another command
    // or perform some other actions. Make sure you do something with the interaction afterwards.
    // [...]
  }
}

Buttons

Handling Buttons is almost as same as handling Select Menus. Both use customId property to tell us what command to execute next or which button was pressed on the bot's message:

client.on("interactionCreate", async (interaction: Interaction): Promise<void> => {
  if (interaction.isButton()) {
    // The following is now your new command name
    const commandName = interaction.customId;
  }
}

Button styles (colors) are explained here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment