Select menus
With the components API, you can create interactive message components. On this page, we'll cover how to send, receive, and respond to select menus using discord.js!
TIP
This page is a follow-up to the slash commands section. Please carefully read those pages first so that you can understand the methods used in this section.
Building and sending select menus
Select menus are one of the MessageComponent
classes, which can be sent via messages or interaction responses. A select menu, as any other message component, must be in an ActionRow
.
WARNING
You can have a maximum of five ActionRow
s per message, and one select menu within an ActionRow
.
To create a select menu, use the ActionRowBuilder
open in new window and StringSelectMenuBuilder
open in new window classes. Then, pass the resulting row object to ChatInputCommandInteraction#reply()
open in new window in the components
array of InteractionReplyOptions
open in new window:
const { ActionRowBuilder, Events, StringSelectMenuBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
const row = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('select')
.setPlaceholder('Nothing selected')
.addOptions(
{
label: 'Select me',
description: 'This is a description',
value: 'first_option',
},
{
label: 'You can select me too',
description: 'This is also a description',
value: 'second_option',
},
),
);
await interaction.reply({ content: 'Pong!', components: [row] });
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
TIP
The custom id is a developer-defined string of up to 100 characters. Use this field to ensure you can uniquely define all incoming interactions from your select menus!
Restart your bot and then send the command to a channel your bot has access to. If all goes well, you should see something like this:
You can also send message components within an ephemeral response or alongside message embeds.
const { ActionRowBuilder, EmbedBuilder, Events, StringSelectMenuBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
const row = new ActionRowBuilder()
.addComponents(
// ...
);
const embed = new EmbedBuilder()
.setColor(0x0099FF)
.setTitle('Some title')
.setURL('https://discord.js.org/')
.setDescription('Some description here');
await interaction.reply({ content: 'Pong!', ephemeral: true, embeds: [embed], components: [row] });
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Restart your bot and then send the command to a channel your bot has access to. If all goes well, you should see something like this:
WARNING
If you're using TypeScript you'll need to specify the type of components your action row holds. This can be done by specifying the component builder you will add to it using a generic parameter in ActionRowBuilder
open in new window.
- new ActionRowBuilder()
+ new ActionRowBuilder<StringSelectMenuBuilder>()
2
Now you know all there is to building and sending a SelectMenu
! Let's move on to how to receive menus!
Receiving select menu interactions
Component collectors
Message component interactions can be collected within the scope of the slash command that sent them by utilising an InteractionCollector
open in new window, or their promisified awaitMessageComponent
variant. These both provide instances of the MessageComponentInteraction
open in new window class as collected items.
TIP
You can create the collectors on either a message
or a channel
.
For a detailed guide on receiving message components via collectors, please refer to the collectors guide.
The interactionCreate event
To receive a StringSelectMenuInteraction
open in new window, attach an Client#event:interactionCreate
open in new window event listener to your client and use the BaseInteraction#isStringSelectMenu()
open in new window type guard to make sure you only receive select menus:
client.on(Events.InteractionCreate, interaction => {
if (!interaction.isStringSelectMenu()) return;
console.log(interaction);
});
2
3
4
Responding to select menus
The MessageComponentInteraction
open in new window class provides the same methods as the ChatInputCommandInteraction
open in new window class. These methods behave equally:
reply()
editReply()
deferReply()
fetchReply()
deleteReply()
followUp()
Updating the select menu's message
The MessageComponentInteraction
open in new window class provides an MessageComponentInteraction#update()
open in new window method to update the message the select menu is attached to. Passing an empty array to the components
option will remove any menus after an option has been selected.
This method should be used in favour of editReply()
on the original interaction, to ensure you respond to the select menu interaction.
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isStringSelectMenu()) return;
if (interaction.customId === 'select') {
await interaction.update({ content: 'Something was selected!', components: [] });
}
});
2
3
4
5
6
7
Deferring and updating the select menu's message
Additionally to deferring the response of the interaction, you can defer the menu, which will trigger a loading state and then revert back to its original state:
const wait = require('node:timers/promises').setTimeout;
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isStringSelectMenu()) return;
if (interaction.customId === 'select') {
await interaction.deferUpdate();
await wait(4000);
await interaction.editReply({ content: 'Something was selected!', components: [] });
}
});
2
3
4
5
6
7
8
9
10
11
Multi-select menus
A select menu is not bound to only one selection; you can specify a minimum and maximum amount of options that must be selected. You can use StringSelectMenuBuilder#setMinValues()
open in new window and StringSelectMenuBuilder#setMaxValues()
open in new window to determine these values.
const { ActionRowBuilder, Events, StringSelectMenuBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
const row = new ActionRowBuilder()
.addComponents(
new StringSelectMenuBuilder()
.setCustomId('select')
.setPlaceholder('Nothing selected')
.setMinValues(2)
.setMaxValues(3)
.addOptions([
{
label: 'Select me',
description: 'This is a description',
value: 'first_option',
},
{
label: 'You can select me too',
description: 'This is also a description',
value: 'second_option',
},
{
label: 'I am also an option',
description: 'This is a description as well',
value: 'third_option',
},
]),
);
await interaction.reply({ content: 'Pong!', components: [row] });
}
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Accessing select menu interaction values
After receiving your StringSelectMenuInteraction
open in new window, you will be able to access the selected values from StringSelectMenuInteraction#values
open in new window. This will return an array of string values associated with the selected options in your select menu.
By default, select menus only accept a single selection. You can retrieve the selected value by accessing the first index of the returned array, as demonstrated in the snippet below:
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isStringSelectMenu()) return;
const selected = interaction.values[0];
if (selected === 'ping') {
await interaction.update('The Ping option has been selected!');
} else if (selected === 'pong') {
await interaction.update('The Pong option has been selected!');
}
});
2
3
4
5
6
7
8
9
10
11
In the case of a multi-select menu, the received StringSelectMenuInteraction#values
open in new window may contain more than one value, and should be handled accordingly:
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isStringSelectMenu()) return;
const selected = interaction.values.join(', ');
await interaction.update(`The user selected ${selected}!`);
});
2
3
4
5
6
7