Components

Modals

Learn how to create and use modal components with djs-core.

Modals are form-like dialogs that allow users to input text through text input fields. They're commonly used for collecting user information or complex data.

Modals can have up to 5 text input fields and must be opened from a button or other interaction. They cannot be sent directly in a message.

Creating a Modal

Modals in djs-core are created using the Modal class. Each modal component file in src/components/modals/ is automatically registered.

Modals are perfect for multi-step forms, user registration, feedback collection, or any scenario where you need structured text input from users.

Basic Modal

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("Feedback Form")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("feedback")
                    .setLabel("Your feedback")
                    .setStyle(TextInputStyle.Paragraph)
                    .setRequired(true),
            ),
    )
    .setCustomId("feedback-modal")
    .run(async (interaction) => {
        const feedback = interaction.fields.getTextInputValue("feedback");
        await interaction.reply(`Thank you for your feedback: ${feedback}`);
    });

Text Input Styles

Discord supports two text input styles, each suited for different use cases:

TextInputStyle.Short
TextInputStyle
Single-line text input (max 4000 characters) - Perfect for names, emails, titles, or short answers
TextInputStyle.Paragraph
TextInputStyle
Multi-line text input (max 4000 characters) - Ideal for descriptions, feedback, messages, or longer content

Short Text Input

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("Enter Name")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("name")
                    .setLabel("Name")
                    .setStyle(TextInputStyle.Short)
                    .setRequired(true),
            ),
    )
    .setCustomId("name-modal")
    .run(async (interaction) => {
        const name = interaction.fields.getTextInputValue("name");
        await interaction.reply(`Hello, ${name}!`);
    });

Paragraph Text Input

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("Report Issue")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("description")
                    .setLabel("Describe the issue")
                    .setStyle(TextInputStyle.Paragraph)
                    .setPlaceholder("Enter details here...")
                    .setRequired(true),
            ),
    )
    .setCustomId("report-modal")
    .run(async (interaction) => {
        const description = interaction.fields.getTextInputValue("description");
        await interaction.reply(`Issue reported: ${description}`);
    });

Multiple Input Fields

Modals can have up to 5 text input fields:

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("User Registration")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("username")
                    .setLabel("Username")
                    .setStyle(TextInputStyle.Short)
                    .setRequired(true),
            ),
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("email")
                    .setLabel("Email")
                    .setStyle(TextInputStyle.Short)
                    .setRequired(true),
            ),
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("bio")
                    .setLabel("Bio")
                    .setStyle(TextInputStyle.Paragraph)
                    .setRequired(false),
            ),
    )
    .setCustomId("register-modal")
    .run(async (interaction) => {
        const username = interaction.fields.getTextInputValue("username");
        const email = interaction.fields.getTextInputValue("email");
        const bio = interaction.fields.getTextInputValue("bio") || "No bio";
        
        await interaction.reply(
            `Registered: ${username} (${email})\nBio: ${bio}`,
        );
    });

Opening Modals from Buttons

Modals are typically opened from buttons or other interactions:

import { Command, Button, Modal } from "@djs-core/runtime";
import { ButtonStyle, ActionRowBuilder, TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";
import feedbackModal from "../../components/modals/feedback";

const openModalButton = new Button()
    .setLabel("Open Feedback Form")
    .setStyle(ButtonStyle.Primary)
    .setCustomId("open-feedback")
    .run(async (interaction) => {
        await interaction.showModal(feedbackModal);
    });

export default new Command()
    .setDescription("Get feedback")
    .run(async (interaction) => {
        const row = new ActionRowBuilder<Button>().addComponents(openModalButton);
        
        await interaction.reply({
            content: "Click the button to provide feedback:",
            components: [row],
        });
    });

Modals can receive custom data. Important: The data is not set in the component definition, but when you show the modal from a button or command.

Component Definition (without data)

src/components/modals/edit-profile.ts
import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal<{ userId: string }>()
    .setTitle("Edit Profile")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("bio")
                    .setLabel("New Bio")
                    .setStyle(TextInputStyle.Paragraph),
            ),
    )
    .setCustomId("edit-profile-modal")
    .run(async (interaction, data) => {
        const bio = interaction.fields.getTextInputValue("bio");
        await interaction.reply(
            `Updated bio for user ${data.userId}: ${bio}`,
        );
    });

Using Modal with Data

When showing the modal from a button, set the data:

src/interactions/commands/edit.ts
import { Command, Button, Modal } from "@djs-core/runtime";
import { ButtonStyle, ActionRowBuilder } from "discord.js";
import editProfileModal from "../../components/modals/edit-profile";

const openModalButton = new Button()
    .setLabel("Edit Profile")
    .setStyle(ButtonStyle.Primary)
    .setCustomId("open-edit-modal")
    .run(async (interaction) => {
        const userId = interaction.user.id;
        // Set data when showing the modal
        await interaction.showModal(
            editProfileModal.setData({ userId }),
        );
    });

export default new Command()
    .setDescription("Edit your profile")
    .run(async (interaction) => {
        const row = new ActionRowBuilder<Button>().addComponents(openModalButton);
        
        await interaction.reply({
            content: "Click the button to edit your profile:",
            components: [row],
        });
    });

Placeholders and Default Values

Text inputs can have placeholders and default values:

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("Create Post")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("title")
                    .setLabel("Title")
                    .setStyle(TextInputStyle.Short)
                    .setPlaceholder("Enter post title...")
                    .setRequired(true),
            ),
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("content")
                    .setLabel("Content")
                    .setStyle(TextInputStyle.Paragraph)
                    .setPlaceholder("Write your post content here...")
                    .setValue("Default content")
                    .setRequired(true),
            ),
    )
    .setCustomId("create-post-modal")
    .run(async (interaction) => {
        const title = interaction.fields.getTextInputValue("title");
        const content = interaction.fields.getTextInputValue("content");
        await interaction.reply(`Post created: ${title}\n${content}`);
    });

Min and Max Length

You can set minimum and maximum lengths for text inputs:

import { Modal } from "@djs-core/runtime";
import { TextInputBuilder, TextInputStyle, LabelBuilder } from "discord.js";

export default new Modal()
    .setTitle("Submit Code")
    .addLabelComponents(
        new LabelBuilder()
            .setTextInputComponent(
                new TextInputBuilder()
                    .setCustomId("code")
                    .setLabel("Code")
                    .setStyle(TextInputStyle.Paragraph)
                    .setMinLength(10)
                    .setMaxLength(1000)
                    .setRequired(true),
            ),
    )
    .setCustomId("code-modal")
    .run(async (interaction) => {
        const code = interaction.fields.getTextInputValue("code");
        await interaction.reply(`Code received (${code.length} characters)`);
    });