Modals
With modals you can create pop-up forms that allow users to provide you with formatted inputs through submissions. We'll cover how to create, show, and receive modal forms using discord.js!
TIP
This page is a follow-up to the interactions (slash commands) page. Please carefully read that section first, so that you can understand the methods used in this section.
Building and responding with modals
Unlike message components, modals aren't strictly components themselves. They're a callback structure used to respond to interactions.
TIP
You can have a maximum of five ActionRowBuilder
open in new windows per modal builder, and one TextInputBuilder
open in new window within an ActionRowBuilder
open in new window. Currently, you can only use TextInputBuilder
open in new windows in modal action rows builders.
To create a modal you construct a new ModalBuilder
open in new window. You can then use the setters to add the custom id and title.
const { Events, ModalBuilder } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
const modal = new ModalBuilder()
.setCustomId('myModal')
.setTitle('My Modal');
// TODO: Add components to modal...
}
});
2
3
4
5
6
7
8
9
10
11
12
13
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 modals!
The next step is to add the input fields in which users responding can enter free-text. Adding inputs is similar to adding components to messages.
At the end, we then call ChatInputCommandInteraction#showModal()
open in new window to display the modal to the user.
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 generic parameter in ActionRowBuilder
open in new window
- new ActionRowBuilder()
+ new ActionRowBuilder<ModalActionRowComponentBuilder>()
2
const { ActionRowBuilder, Events, ModalBuilder, TextInputBuilder, TextInputStyle } = require('discord.js');
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'ping') {
// Create the modal
const modal = new ModalBuilder()
.setCustomId('myModal')
.setTitle('My Modal');
// Add components to modal
// Create the text input components
const favoriteColorInput = new TextInputBuilder()
.setCustomId('favoriteColorInput')
// The label is the prompt the user sees for this input
.setLabel("What's your favorite color?")
// Short means only a single line of text
.setStyle(TextInputStyle.Short);
const hobbiesInput = new TextInputBuilder()
.setCustomId('hobbiesInput')
.setLabel("What's some of your favorite hobbies?")
// Paragraph means multiple lines of text.
.setStyle(TextInputStyle.Paragraph);
// An action row only holds one text input,
// so you need one action row per text input.
const firstActionRow = new ActionRowBuilder().addComponents(favoriteColorInput);
const secondActionRow = new ActionRowBuilder().addComponents(hobbiesInput);
// Add inputs to the modal
modal.addComponents(firstActionRow, secondActionRow);
// Show the modal to the user
await interaction.showModal(modal);
}
});
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
36
37
38
39
Restart your bot and invoke the /ping
command again. You should see a popup form resembling the image below:
WARNING
Showing a modal must be the first response to an interaction. You cannot defer()
or deferUpdate()
then show a modal later.
Input styles
Currently there are two different input styles available:
Short
, a single-line text entry;Paragraph
, a multi-line text entry similar to the HTML<textarea>
;
Input properties
In addition to the customId
, label
and style
, a text input can be customised in a number of ways to apply validation, prompt the user, or set default values via the TextInputBuilder
open in new window methods:
const input = new TextInputBuilder()
// set the maximum number of characters to allow
.setMaxLength(1_000)
// set the minimum number of characters required for submission
.setMinLength(10)
// set a placeholder string to prompt the user
.setPlaceholder('Enter some text!')
// set a default value to pre-fill the input
.setValue('Default')
// require a value in this input field
.setRequired(true);
2
3
4
5
6
7
8
9
10
11
Receiving modal submissions
Interaction collectors
Modal submissions can be collected within the scope of the interaction that showed it by utilising an InteractionCollector
open in new window, or the ChatInputCommandInteraction#awaitModalSubmit()
open in new window promisified method. These both provide instances of the ModalSubmitInteraction
open in new window class as collected items.
For a detailed guide on receiving message components via collectors, please refer to the collectors guide.
The interactionCreate event
To receive a ModalSubmitInteraction
open in new window event, attach an Client#interactionCreate
open in new window event listener to your client and use the BaseInteraction#isModalSubmit()
open in new window type guard to make sure you only receive modals:
client.on(Events.InteractionCreate, interaction => {
if (!interaction.isModalSubmit()) return;
console.log(interaction);
});
2
3
4
Responding to modal submissions
The ModalSubmitInteraction
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()
If the modal was shown from a ButtonInteraction
open in new window or StringSelectMenuInteraction
open in new window, it will also provide these methods, which behave equally:
update()
deferUpdate()
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isModalSubmit()) return;
if (interaction.customId === 'myModal') {
await interaction.reply({ content: 'Your submission was received successfully!' });
}
});
2
3
4
5
6
TIP
If you're using typescript, you can use the ModalSubmitInteraction#isFromMessage()
open in new window typeguard, to make sure the received interaction was from a MessageComponentInteraction
.
Extracting data from modal submissions
You'll most likely need to read the data sent by the user in the modal. You can do this by accessing the ModalSubmitInteraction#fields
open in new window. From there you can call ModalSubmitFields#getTextInputValue()
open in new window with the custom id of the text input to get the value.
client.on(Events.InteractionCreate, interaction => {
if (!interaction.isModalSubmit()) return;
// Get the data entered by the user
const favoriteColor = interaction.fields.getTextInputValue('favoriteColorInput');
const hobbies = interaction.fields.getTextInputValue('hobbiesInput');
console.log({ favoriteColor, hobbies });
});
2
3
4
5
6
7
8