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 in djs-core are created using the Modal class. Each modal component file in src/components/modals/ is automatically registered.
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}`);
});
Discord supports two text input styles, each suited for different use cases:
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}!`);
});
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}`);
});
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}`,
);
});
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.
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}`,
);
});
When showing the modal from a button, set the data:
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],
});
});
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}`);
});
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)`);
});