Modales (formularios)
Un modal es una ventana emergente con un formulario. Sirve para pedir al usuario textos, opciones o archivos.
Envíanos tu opinión
¿Qué te ha parecido el bot?*
Cuéntanos lo que quieras, en pocas palabras.
Me encanta porque...
Puntuación*
Elige una puntuación
{ "type": 9, "data": { "custom_id": "feedback_modal", "title": "Envíanos tu opinión", "components": [ { "type": 18, "label": "¿Qué te ha parecido el bot?", "description": "Cuéntanos lo que quieras, en pocas palabras.", "component": { "type": 4, "custom_id": "opinion", "style": 2, "placeholder": "Me encanta porque...", "max_length": 1000 } }, { "type": 18, "label": "Puntuación", "component": { "type": 3, "custom_id": "puntuacion", "placeholder": "Elige una puntuación", "options": [ { "label": "⭐⭐⭐⭐⭐ Excelente", "value": "5" }, { "label": "⭐⭐⭐ Normal", "value": "3" }, { "label": "⭐ Mejorable", "value": "1" } ] } } ] }}Cómo funciona
Sección titulada «Cómo funciona»- El usuario usa un comando o pulsa un botón.
- Tu bot responde con el tipo de respuesta
MODAL(9). Abrir el modal es la respuesta, así que tiene que ser lo primero que hagas. - El usuario rellena el formulario y pulsa Enviar.
- Tu bot recibe una interacción
MODAL_SUBMIT(5) con los valores, y responde (por ejemplo con un mensaje).
Estructura del modal
Sección titulada «Estructura del modal»| Campo | Tipo | Descripción |
|---|---|---|
custom_id |
string | obligatorio Identifica el modal al recibir la respuesta (1-100 caracteres). |
title |
string | obligatorio Título de la ventana (máx. 45 caracteres). |
components |
array | obligatorio Entre 1 y 5 componentes: Label o Text Display. |
¿Qué se puede poner en un modal?
Sección titulada «¿Qué se puede poner en un modal?»| Componente | Cómo se usa |
|---|---|
| Text Display | Directamente, para mostrar texto explicativo. |
| Label | Envuelve a un componente de entrada y le pone título y descripción. |
| Text Input | Dentro de un Label. |
| String Select y menús automáticos | Dentro de un Label. |
| File Upload | Dentro de un Label. |
| Radio Group, Checkbox Group y Checkbox | Dentro de un Label. |
Abrir el modal
Sección titulada «Abrir el modal»Envía el JSON de arriba (el que empieza con "type": 9) a:
POST /interactions/{interaction.id}/{interaction.token}/callback// showModal acepta el objeto "data" del modal en JSONawait interaction.showModal({ custom_id: 'feedback_modal', title: 'Envíanos tu opinión', components: [ { type: 18, label: '¿Qué te ha parecido el bot?', description: 'Cuéntanos lo que quieras, en pocas palabras.', component: { type: 4, custom_id: 'opinion', style: 2, placeholder: 'Me encanta porque...', max_length: 1000 }, }, { type: 18, label: 'Puntuación', component: { type: 3, custom_id: 'puntuacion', placeholder: 'Elige una puntuación', options: [ { label: '⭐⭐⭐⭐⭐ Excelente', value: '5' }, { label: '⭐⭐⭐ Normal', value: '3' }, { label: '⭐ Mejorable', value: '1' }, ], }, }, ],});Las versiones recientes de discord.js también tienen ModalBuilder y LabelBuilder si prefieres builders.
import discord
class FeedbackModal(discord.ui.Modal, title="Envíanos tu opinión"): opinion = discord.ui.Label( text="¿Qué te ha parecido el bot?", description="Cuéntanos lo que quieras, en pocas palabras.", component=discord.ui.TextInput( style=discord.TextStyle.paragraph, placeholder="Me encanta porque...", max_length=1000, ), ) puntuacion = discord.ui.Label( text="Puntuación", component=discord.ui.Select( placeholder="Elige una puntuación", options=[ discord.SelectOption(label="⭐⭐⭐⭐⭐ Excelente", value="5"), discord.SelectOption(label="⭐⭐⭐ Normal", value="3"), discord.SelectOption(label="⭐ Mejorable", value="1"), ], ), )
async def on_submit(self, interaction: discord.Interaction): texto = self.opinion.component.value nota = self.puntuacion.component.values[0] await interaction.response.send_message(f"¡Gracias! Nota: {nota}", ephemeral=True)
# Dentro de un comando o del callback de un botón:await interaction.response.send_modal(FeedbackModal())s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseModal, // tipo 9 Data: &discordgo.InteractionResponseData{ CustomID: "feedback_modal", Title: "Envíanos tu opinión", Components: []discordgo.MessageComponent{ discordgo.Label{ Label: "¿Qué te ha parecido el bot?", Description: "Cuéntanos lo que quieras, en pocas palabras.", Component: discordgo.TextInput{ CustomID: "opinion", Style: discordgo.TextInputParagraph, Placeholder: "Me encanta porque...", MaxLength: 1000, }, }, discordgo.Label{ Label: "Puntuación", Component: discordgo.SelectMenu{ MenuType: discordgo.StringSelectMenu, CustomID: "puntuacion", Placeholder: "Elige una puntuación", Options: []discordgo.SelectMenuOption{ {Label: "⭐⭐⭐⭐⭐ Excelente", Value: "5"}, {Label: "⭐⭐⭐ Normal", Value: "3"}, {Label: "⭐ Mejorable", Value: "1"}, }, }, }, }, },})Leer las respuestas
Sección titulada «Leer las respuestas»Cuando el usuario pulsa Enviar, recibes una interacción MODAL_SUBMIT (5). Cada Label llega con su componente y el valor introducido:
{ "custom_id": "feedback_modal", "components": [ { "type": 18, "id": 1, "component": { "type": 4, "id": 2, "custom_id": "opinion", "value": "¡Me encanta!" } }, { "type": 18, "id": 3, "component": { "type": 3, "id": 4, "custom_id": "puntuacion", "values": ["5"] } } ]}| Componente | Dónde está el valor |
|---|---|
| Text Input | value (string) |
| Selects | values (array de strings con los valores o IDs) |
| File Upload | values (IDs de adjuntos; los datos completos están en resolved.attachments) |
| Radio Group | value (string, o null si no se eligió nada) |
| Checkbox Group | values (array de strings) |
| Checkbox | value (true o false) |
Recorre data.components, busca el custom_id que quieres dentro de cada component y lee su value o values.
client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isModalSubmit() || interaction.customId !== 'feedback_modal') return;
const opinion = interaction.fields.getTextInputValue('opinion'); const nota = interaction.fields.getStringSelectValues('puntuacion')[0];
await interaction.reply({ content: `¡Gracias! Nota: ${nota}`, flags: MessageFlags.Ephemeral });});El método on_submit del modal (ver arriba) recibe los valores ya cargados en cada componente:
self.opinion.component.value y self.puntuacion.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("opinión:", input.Value) case *discordgo.SelectMenu: log.Println("nota:", input.Values) } } }}Siguiente paso
Sección titulada «Siguiente paso»Si vienes de los embeds, mira la guía de migración.