CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-discordjs--builders

Type-safe builders for Discord API payloads including slash commands, embeds, buttons, select menus, and message components

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

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

Install with Tessl CLI

npx tessl i tessl/npm-discordjs--builders

docs

assertions.md

components-v2.md

context-menu-commands.md

embeds.md

index.md

message-components.md

modals.md

select-menus.md

slash-commands.md

utilities.md

tile.json