Modals are popup forms that can be displayed to users for collecting input through text fields. They provide a focused interface for gathering structured information.
Main builder for creating Discord modals.
class ModalBuilder {
readonly data: Partial<APIModalInteractionResponseCallbackData>;
readonly components: ActionRowBuilder<ModalActionRowComponentBuilder>[];
constructor(data?: Partial<APIModalInteractionResponseCallbackData>);
setTitle(title: string): this;
setCustomId(customId: string): this;
addComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>): this;
setComponents(...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>): this;
spliceComponents(index: number, deleteCount: number, ...components: RestOrArray<ActionRowBuilder<ModalActionRowComponentBuilder>>): this;
toJSON(): APIModalInteractionResponseCallbackData;
}Modals can only contain text input components organized in action rows.
type ModalActionRowComponentBuilder = TextInputBuilder;
type ModalComponentBuilder =
| ActionRowBuilder<ModalActionRowComponentBuilder>
| ModalActionRowComponentBuilder;import { ModalBuilder, TextInputBuilder, ActionRowBuilder, TextInputStyle } from "@discordjs/builders";
const modal = new ModalBuilder()
.setCustomId('feedback_modal')
.setTitle('Feedback Form');
const nameInput = new TextInputBuilder()
.setCustomId('user_name')
.setLabel('Your Name')
.setStyle(TextInputStyle.Short)
.setMinLength(2)
.setMaxLength(50)
.setRequired(true);
const feedbackInput = new TextInputBuilder()
.setCustomId('feedback_text')
.setLabel('Your Feedback')
.setStyle(TextInputStyle.Paragraph)
.setMinLength(10)
.setMaxLength(1000)
.setPlaceholder('Tell us what you think...')
.setRequired(true);
const firstRow = new ActionRowBuilder<TextInputBuilder>()
.addComponents(nameInput);
const secondRow = new ActionRowBuilder<TextInputBuilder>()
.addComponents(feedbackInput);
modal.addComponents(firstRow, secondRow);const contactModal = new ModalBuilder()
.setCustomId('contact_form')
.setTitle('Contact Us');
const emailInput = new TextInputBuilder()
.setCustomId('email')
.setLabel('Email Address')
.setStyle(TextInputStyle.Short)
.setPlaceholder('your.email@example.com')
.setRequired(true);
const subjectInput = new TextInputBuilder()
.setCustomId('subject')
.setLabel('Subject')
.setStyle(TextInputStyle.Short)
.setMaxLength(100)
.setRequired(true);
const messageInput = new TextInputBuilder()
.setCustomId('message')
.setLabel('Message')
.setStyle(TextInputStyle.Paragraph)
.setMinLength(20)
.setMaxLength(2000)
.setPlaceholder('Describe your inquiry in detail...')
.setRequired(true);
contactModal.setComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(emailInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(subjectInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(messageInput)
);const surveyModal = new ModalBuilder()
.setCustomId('user_survey')
.setTitle('User Experience Survey');
const ageInput = new TextInputBuilder()
.setCustomId('age')
.setLabel('Age (optional)')
.setStyle(TextInputStyle.Short)
.setMinLength(1)
.setMaxLength(3)
.setPlaceholder('e.g., 25')
.setRequired(false);
const experienceInput = new TextInputBuilder()
.setCustomId('experience')
.setLabel('How long have you been using our service?')
.setStyle(TextInputStyle.Short)
.setMaxLength(50)
.setPlaceholder('e.g., 6 months')
.setRequired(true);
const improvementInput = new TextInputBuilder()
.setCustomId('improvements')
.setLabel('What could we improve?')
.setStyle(TextInputStyle.Paragraph)
.setMinLength(10)
.setMaxLength(500)
.setRequired(true);
const ratingInput = new TextInputBuilder()
.setCustomId('rating')
.setLabel('Rate our service (1-10)')
.setStyle(TextInputStyle.Short)
.setMinLength(1)
.setMaxLength(2)
.setPlaceholder('e.g., 8')
.setRequired(true);
const recommendInput = new TextInputBuilder()
.setCustomId('recommend')
.setLabel('Would you recommend us to others? Why?')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(300)
.setRequired(true);
surveyModal.setComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(ageInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(experienceInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(improvementInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(ratingInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(recommendInput)
);const editProfileModal = new ModalBuilder()
.setCustomId('edit_profile')
.setTitle('Edit Profile');
// Pre-fill with existing user data
const displayNameInput = new TextInputBuilder()
.setCustomId('display_name')
.setLabel('Display Name')
.setStyle(TextInputStyle.Short)
.setMaxLength(32)
.setValue('Current Display Name') // Pre-filled value
.setRequired(true);
const bioInput = new TextInputBuilder()
.setCustomId('bio')
.setLabel('Bio')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(500)
.setValue('Current bio text here...') // Pre-filled value
.setPlaceholder('Tell others about yourself')
.setRequired(false);
editProfileModal.setComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(displayNameInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(bioInput)
);function createReportModal(reportType: string): ModalBuilder {
const modal = new ModalBuilder()
.setCustomId(`report_${reportType}`)
.setTitle(`Report ${reportType}`);
const reasonInput = new TextInputBuilder()
.setCustomId('reason')
.setLabel('Reason for report')
.setStyle(TextInputStyle.Paragraph)
.setMinLength(10)
.setMaxLength(1000)
.setPlaceholder(`Explain why you are reporting this ${reportType}...`)
.setRequired(true);
const detailsInput = new TextInputBuilder()
.setCustomId('details')
.setLabel('Additional Details (optional)')
.setStyle(TextInputStyle.Paragraph)
.setMaxLength(500)
.setPlaceholder('Any additional context or evidence...')
.setRequired(false);
modal.setComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(reasonInput),
new ActionRowBuilder<TextInputBuilder>().addComponents(detailsInput)
);
return modal;
}
// Usage
const userReportModal = createReportModal('user');
const messageReportModal = createReportModal('message');const modal = new ModalBuilder()
.setCustomId('dynamic_modal')
.setTitle('Dynamic Form');
// Create text inputs
const input1 = new TextInputBuilder()
.setCustomId('field1')
.setLabel('Field 1')
.setStyle(TextInputStyle.Short)
.setRequired(true);
const input2 = new TextInputBuilder()
.setCustomId('field2')
.setLabel('Field 2')
.setStyle(TextInputStyle.Short)
.setRequired(true);
// Add components
modal.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(input1),
new ActionRowBuilder<TextInputBuilder>().addComponents(input2)
);
// Replace a component
const newInput = new TextInputBuilder()
.setCustomId('field2_updated')
.setLabel('Updated Field 2')
.setStyle(TextInputStyle.Paragraph)
.setRequired(false);
modal.spliceComponents(1, 1,
new ActionRowBuilder<TextInputBuilder>().addComponents(newInput)
);
// Set all components at once
const finalInput = new TextInputBuilder()
.setCustomId('final_field')
.setLabel('Final Field')
.setStyle(TextInputStyle.Short)
.setRequired(true);
modal.setComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(finalInput)
);Discord enforces these limits on modals:
Modals are typically shown in response to button or slash command interactions:
// In an interaction handler (pseudocode)
if (interaction.isButton() && interaction.customId === 'show_modal') {
const modal = new ModalBuilder()
.setCustomId('example_modal')
.setTitle('Example Form')
.addComponents(
new ActionRowBuilder<TextInputBuilder>().addComponents(
new TextInputBuilder()
.setCustomId('input_field')
.setLabel('Enter something')
.setStyle(TextInputStyle.Short)
.setRequired(true)
)
);
await interaction.showModal(modal);
}