Modals (forms)
A modal is a pop-up window with a form. Use it to ask the user for text, choices or files.
Send us your feedback
What do you think of the bot?*
Tell us anything, in a few words.
I love it because...
Rating*
Pick a rating
{ "type": 9, "data": { "custom_id": "feedback_modal", "title": "Send us your feedback", "components": [ { "type": 18, "label": "What do you think of the bot?", "description": "Tell us anything, in a few words.", "component": { "type": 4, "custom_id": "opinion", "style": 2, "placeholder": "I love it because...", "max_length": 1000 } }, { "type": 18, "label": "Rating", "component": { "type": 3, "custom_id": "rating", "placeholder": "Pick a rating", "options": [ { "label": "⭐⭐⭐⭐⭐ Excellent", "value": "5" }, { "label": "⭐⭐⭐ Okay", "value": "3" }, { "label": "⭐ Needs work", "value": "1" } ] } } ] }}How it works
Section titled “How it works”- The user runs a command or clicks a button.
- Your bot responds with the
MODAL(9) response type. Opening the modal is the response, so it must be the first thing you do. - The user fills in the form and clicks Submit.
- Your bot receives a
MODAL_SUBMIT(5) interaction with the values, and responds (for example with a message).
Modal structure
Section titled “Modal structure”| Field | Type | Description |
|---|---|---|
custom_id |
string | required Identifies the modal when you receive the answer (1-100 characters). |
title |
string | required Window title (max 45 characters). |
components |
array | required Between 1 and 5 components: Label or Text Display. |
What can go in a modal?
Section titled “What can go in a modal?”| Component | How it’s used |
|---|---|
| Text Display | Directly, to show explanatory text. |
| Label | Wraps one input component and gives it a title and description. |
| Text Input | Inside a Label. |
| String Select and auto-populated menus | Inside a Label. |
| File Upload | Inside a Label. |
| Radio Group, Checkbox Group and Checkbox | Inside a Label. |
Open the modal
Section titled “Open the modal”Send the JSON above (the one starting with "type": 9) to:
POST /interactions/{interaction.id}/{interaction.token}/callback// showModal accepts the modal's "data" object as JSONawait interaction.showModal({ custom_id: 'feedback_modal', title: 'Send us your feedback', components: [ { type: 18, label: 'What do you think of the bot?', description: 'Tell us anything, in a few words.', component: { type: 4, custom_id: 'opinion', style: 2, placeholder: 'I love it because...', max_length: 1000 }, }, { type: 18, label: 'Rating', component: { type: 3, custom_id: 'rating', placeholder: 'Pick a rating', options: [ { label: '⭐⭐⭐⭐⭐ Excellent', value: '5' }, { label: '⭐⭐⭐ Okay', value: '3' }, { label: '⭐ Needs work', value: '1' }, ], }, }, ],});Recent discord.js versions also have ModalBuilder and LabelBuilder if you prefer builders.
import discord
class FeedbackModal(discord.ui.Modal, title="Send us your feedback"): opinion = discord.ui.Label( text="What do you think of the bot?", description="Tell us anything, in a few words.", component=discord.ui.TextInput( style=discord.TextStyle.paragraph, placeholder="I love it because...", max_length=1000, ), ) rating = discord.ui.Label( text="Rating", component=discord.ui.Select( placeholder="Pick a rating", options=[ discord.SelectOption(label="⭐⭐⭐⭐⭐ Excellent", value="5"), discord.SelectOption(label="⭐⭐⭐ Okay", value="3"), discord.SelectOption(label="⭐ Needs work", value="1"), ], ), )
async def on_submit(self, interaction: discord.Interaction): text = self.opinion.component.value score = self.rating.component.values[0] await interaction.response.send_message(f"Thanks! Rating: {score}", ephemeral=True)
# Inside a command or a button callback:await interaction.response.send_modal(FeedbackModal())s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseModal, // type 9 Data: &discordgo.InteractionResponseData{ CustomID: "feedback_modal", Title: "Send us your feedback", Components: []discordgo.MessageComponent{ discordgo.Label{ Label: "What do you think of the bot?", Description: "Tell us anything, in a few words.", Component: discordgo.TextInput{ CustomID: "opinion", Style: discordgo.TextInputParagraph, Placeholder: "I love it because...", MaxLength: 1000, }, }, discordgo.Label{ Label: "Rating", Component: discordgo.SelectMenu{ MenuType: discordgo.StringSelectMenu, CustomID: "rating", Placeholder: "Pick a rating", Options: []discordgo.SelectMenuOption{ {Label: "⭐⭐⭐⭐⭐ Excellent", Value: "5"}, {Label: "⭐⭐⭐ Okay", Value: "3"}, {Label: "⭐ Needs work", Value: "1"}, }, }, }, }, },})Reading the answers
Section titled “Reading the answers”When the user clicks Submit, you receive a MODAL_SUBMIT (5) interaction. Each Label arrives with its component and the entered value:
{ "custom_id": "feedback_modal", "components": [ { "type": 18, "id": 1, "component": { "type": 4, "id": 2, "custom_id": "opinion", "value": "I love it!" } }, { "type": 18, "id": 3, "component": { "type": 3, "id": 4, "custom_id": "rating", "values": ["5"] } } ]}| Component | Where the value is |
|---|---|
| Text Input | value (string) |
| Selects | values (array of strings with the values or IDs) |
| File Upload | values (attachment IDs; full data is in resolved.attachments) |
| Radio Group | value (string, or null if nothing was picked) |
| Checkbox Group | values (array of strings) |
| Checkbox | value (true or false) |
Loop over data.components, find the custom_id you want inside each component and read its value or values.
client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isModalSubmit() || interaction.customId !== 'feedback_modal') return;
const opinion = interaction.fields.getTextInputValue('opinion'); const score = interaction.fields.getStringSelectValues('rating')[0];
await interaction.reply({ content: `Thanks! Rating: ${score}`, flags: MessageFlags.Ephemeral });});The modal’s on_submit method (see above) receives the values already loaded into each component:
self.opinion.component.value and self.rating.component.values.
if i.Type == discordgo.InteractionModalSubmit { data := i.ModalSubmitData() if data.CustomID == "feedback_modal" { for _, c := range data.Components { label, ok := c.(*discordgo.Label) if !ok { continue } switch input := label.Component.(type) { case *discordgo.TextInput: log.Println("opinion:", input.Value) case *discordgo.SelectMenu: log.Println("rating:", input.Values) } } }}Next step
Section titled “Next step”If you’re coming from embeds, read the migration guide.