Type-safe builders for Discord API payloads including slash commands, embeds, buttons, select menus, and message components
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
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);
}Install with Tessl CLI
npx tessl i tessl/npm-discordjs--builders