or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

assertions.mdcomponents-v2.mdcontext-menu-commands.mdembeds.mdindex.mdmessage-components.mdmodals.mdselect-menus.mdslash-commands.mdutilities.md
tile.json

modals.mddocs/

Modals

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.

ModalBuilder

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;
}

Modal Components

Modals can only contain text input components organized in action rows.

Supported Component Types

type ModalActionRowComponentBuilder = TextInputBuilder;

type ModalComponentBuilder = 
  | ActionRowBuilder<ModalActionRowComponentBuilder>
  | ModalActionRowComponentBuilder;

Usage Examples

Basic Modal

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);

Contact Form Modal

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)
);

Survey Modal

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)
);

Modal with Pre-filled Values

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)
);

Dynamic Modal Creation

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');

Component Manipulation

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)
);

Modal Limits

Discord enforces these limits on modals:

  • Title: Maximum 45 characters
  • Custom ID: Maximum 100 characters
  • Components: Maximum 5 action rows
  • Text Inputs: Maximum 5 text inputs total
  • Action Row: Must contain exactly 1 text input component

Integration with Interactions

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);
}