Documentation Index Fetch the complete documentation index at: https://mintlify.com/disgoorg/disgo/llms.txt
Use this file to discover all available pages before exploring further.
Modals are pop-up forms that allow you to collect detailed user input. They’re perfect for complex forms, settings, or any scenario where you need multiple inputs from a user.
Creating a modal
Modals can only be sent as a response to an interaction (slash command or button click).
Basic modal structure
event . Modal ( discord . ModalCreate {
CustomID : "feedback_modal" ,
Title : "Submit Feedback" ,
Components : [] discord . LayoutComponent {
discord . NewActionRow (
discord . NewShortTextInput ( "name" , "Your Name" ),
),
discord . NewActionRow (
discord . NewParagraphTextInput ( "feedback" , "Your Feedback" ),
),
},
})
Modals support two types of text inputs:
Short Text Input
Paragraph Text Input
discord . NewShortTextInput ( "username" , "Username" ).
WithRequired ( true ).
WithPlaceholder ( "Enter your username" ).
WithMinLength ( 3 ).
WithMaxLength ( 20 )
Complete modal example
Create a slash command that opens a modal
var commands = [] discord . ApplicationCommandCreate {
discord . SlashCommandCreate {
Name : "feedback" ,
Description : "Submit feedback" ,
},
}
func commandListener ( event * events . ApplicationCommandInteractionCreate ) {
data := event . SlashCommandInteractionData ()
if data . CommandName () == "feedback" {
err := event . Modal ( discord . ModalCreate {
CustomID : "feedback_modal" ,
Title : "Submit Feedback" ,
Components : [] discord . LayoutComponent {
discord . NewActionRow (
discord . NewShortTextInput ( "name" , "Your Name" ).
WithPlaceholder ( "John Doe" ).
WithRequired ( true ),
),
discord . NewActionRow (
discord . NewShortTextInput ( "email" , "Email" ).
WithPlaceholder ( "john@example.com" ).
WithRequired ( false ),
),
discord . NewActionRow (
discord . NewParagraphTextInput ( "feedback" , "Feedback" ).
WithPlaceholder ( "Tell us what you think..." ).
WithMinLength ( 10 ).
WithMaxLength ( 500 ).
WithRequired ( true ),
),
},
})
if err != nil {
event . Client (). Logger . Error ( "error creating modal" , slog . Any ( "err" , err ))
}
}
}
Handle modal submission
func modalListener ( event * events . ModalSubmitInteractionCreate ) {
if event . Data . CustomID == "feedback_modal" {
var content strings . Builder
for component := range event . Data . AllComponents () {
if textInput , ok := component .( discord . TextInputComponent ); ok {
content . WriteString ( textInput . CustomID )
content . WriteString ( ": " )
content . WriteString ( textInput . Value )
content . WriteString ( " \n " )
}
}
err := event . CreateMessage ( discord . MessageCreate {
Content : "Thanks for your feedback! \n\n " + content . String (),
Flags : discord . MessageFlagEphemeral ,
})
if err != nil {
event . Client (). Logger . Error ( "error responding to modal" , slog . Any ( "err" , err ))
}
}
}
Register event listeners
client , err := disgo . New ( token ,
bot . WithDefaultGateway (),
bot . WithEventListenerFunc ( commandListener ),
bot . WithEventListenerFunc ( modalListener ),
)
Full working example
package main
import (
" context "
" log/slog "
" os "
" os/signal "
" syscall "
" github.com/disgoorg/disgo "
" github.com/disgoorg/disgo/bot "
" github.com/disgoorg/disgo/discord "
" github.com/disgoorg/disgo/events "
" github.com/disgoorg/disgo/gateway "
" github.com/disgoorg/disgo/handler "
)
var (
token = os . Getenv ( "disgo_token" )
commands = [] discord . ApplicationCommandCreate {
discord . SlashCommandCreate {
Name : "modal" ,
Description : "brings up a modal" ,
},
}
)
func main () {
client , err := disgo . New ( token ,
bot . WithGatewayConfigOpts ( gateway . WithIntents ( gateway . IntentsNone )),
bot . WithEventListenerFunc ( commandListener ),
bot . WithEventListenerFunc ( modalListener ),
)
if err != nil {
slog . Error ( "error while building disgo instance" , slog . Any ( "err" , err ))
return
}
defer client . Close ( context . TODO ())
if err = handler . SyncCommands ( client , commands , nil ); err != nil {
slog . Error ( "error while registering commands" , slog . Any ( "err" , err ))
}
if err = client . OpenGateway ( context . TODO ()); err != nil {
slog . Error ( "error while connecting to gateway" , slog . Any ( "err" , err ))
}
slog . Info ( "bot is now running. Press CTRL-C to exit." )
s := make ( chan os . Signal , 1 )
signal . Notify ( s , syscall . SIGINT , syscall . SIGTERM , os . Interrupt )
<- s
}
func commandListener ( event * events . ApplicationCommandInteractionCreate ) {
data := event . SlashCommandInteractionData ()
if data . CommandName () == "modal" {
if err := event . Modal ( discord . ModalCreate {
Title : "Modal Title" ,
CustomID : "modal-id" ,
Components : [] discord . LayoutComponent {
discord . NewActionRow (
discord . NewShortTextInput ( "short-text-input" , "Short text" ),
),
discord . NewActionRow (
discord . NewParagraphTextInput ( "paragraph-text-input" , "Paragraph text" ),
),
},
}); err != nil {
event . Client (). Logger . Error ( "error creating modal" , slog . Any ( "err" , err ))
}
}
}
func modalListener ( event * events . ModalSubmitInteractionCreate ) {
var content string
for component := range event . Data . AllComponents () {
if textInput , ok := component .( discord . TextInputComponent ); ok {
content += textInput . CustomID + ": " + textInput . Value + " \n "
}
}
if err := event . CreateMessage ( discord . MessageCreate {
Content : content ,
}); err != nil {
event . Client (). Logger . Error ( "error responding to modal" , slog . Any ( "err" , err ))
}
}
You can also include select menus in modals:
event . Modal ( discord . ModalCreate {
CustomID : "profile_modal" ,
Title : "Update Profile" ,
Components : [] discord . LayoutComponent {
discord . NewActionRow (
discord . NewShortTextInput ( "name" , "Display Name" ),
),
discord . NewActionRow (
discord . NewStringSelectMenu (
"favorite_color" ,
"Favorite Color" ,
discord . NewStringSelectMenuOption ( "Red" , "red" ),
discord . NewStringSelectMenuOption ( "Blue" , "blue" ),
discord . NewStringSelectMenuOption ( "Green" , "green" ),
),
),
},
})
Handle the select menu in the modal submission:
func modalListener ( event * events . ModalSubmitInteractionCreate ) {
for component := range event . Data . AllComponents () {
switch c := component .( type ) {
case discord . TextInputComponent :
// Handle text input
fmt . Println ( c . CustomID , c . Value )
case discord . StringSelectMenuComponent :
// Handle select menu
fmt . Println ( c . CustomID , c . Values )
}
}
}
Using builder pattern
DisGo supports a builder pattern for cleaner modal creation:
event . Modal ( discord . NewModalCreateBuilder ().
SetTitle ( "Survey" ).
SetCustomID ( "survey_modal" ).
AddLabel ( "Question 1" , discord . NewShortTextInput ( "q1" )).
AddLabel ( "Question 2" , discord . NewParagraphTextInput ( "q2" )).
Build (),
)
Using the handler package
The handler package provides cleaner modal routing:
import " github.com/disgoorg/disgo/handler "
r := handler . New ()
r . SlashCommand ( "/feedback" , onFeedbackCommand )
r . Modal ( "/feedback_modal" , onFeedbackSubmit )
func onFeedbackCommand ( _ discord . SlashCommandInteractionData , e * handler . CommandEvent ) error {
return e . Modal ( discord . ModalCreate {
CustomID : "/feedback_modal" ,
Title : "Feedback Form" ,
Components : [] discord . LayoutComponent {
discord . NewActionRow (
discord . NewParagraphTextInput ( "feedback" , "Your Feedback" ),
),
},
})
}
func onFeedbackSubmit ( e * handler . ModalEvent ) error {
return e . CreateMessage ( discord . MessageCreate {
Content : "Thanks for your feedback!" ,
Flags : discord . MessageFlagEphemeral ,
})
}
Best practices
Keep modals simple : Don’t overwhelm users with too many fields (maximum 5)
Use appropriate input types : Short text for names, paragraph for descriptions
Provide clear labels : Make it obvious what each field is for
Set reasonable limits : Use MinLength and MaxLength to validate input
Make fields required : Only when absolutely necessary
Use placeholders : Show examples of valid input
Always respond : Discord requires a response to modal submissions
Validate input : Check submitted data before processing
Limitations
Modals can only be sent in response to interactions
Maximum of 5 action rows per modal
Each text input must be in its own action row
Modals timeout after 15 minutes
Custom IDs have a maximum length of 100 characters
Next steps