Modals
Handling modal submissions and data entry in djs-core.
Modals collect text input from users. Define handlers in src/components/modals/ and open them from a command or button with interaction.showModal().
The modal customId is set automatically from the file path — src/components/modals/feedback.ts becomes feedback, admin/edit.ts becomes admin.edit. Do not call .setCustomId() in the handler file.
Creating a modal handler
import { Modal } from "@djs-core/runtime";import { MessageFlags, ActionRowBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
export default new Modal() .setTitle("Give us feedback") .addComponents( new ActionRowBuilder<TextInputBuilder>().addComponents( new TextInputBuilder() .setCustomId("name-input") .setLabel("Your name") .setStyle(TextInputStyle.Short), ), new ActionRowBuilder<TextInputBuilder>().addComponents( new TextInputBuilder() .setCustomId("feedback-input") .setLabel("Your feedback") .setStyle(TextInputStyle.Paragraph), ), ) .run(async (interaction) => { const name = interaction.fields.getTextInputValue("name-input"); const feedback = interaction.fields.getTextInputValue("feedback-input");
await interaction.reply({ content: `Thanks ${name}! Feedback: ${feedback}`, flags: [MessageFlags.Ephemeral], }); });Opening a modal
Import the modal and pass it to showModal():
import feedbackModal from "../../components/modals/feedback";import { Command } from "@djs-core/runtime";
export default new Command() .setDescription("Open the feedback form") .run(async (interaction) => { await interaction.showModal(feedbackModal); });Passing data with .setData()
Use .withData<T>() on the modal and .setData() when opening it — same pattern as buttons and select menus:
import { Modal } from "@djs-core/runtime";import { ActionRowBuilder, TextInputBuilder, TextInputStyle } from "discord.js";
export default new Modal() .withData<{ userId: string }>() .setTitle("Edit nickname") .addComponents( new ActionRowBuilder<TextInputBuilder>().addComponents( new TextInputBuilder() .setCustomId("nickname-input") .setLabel("New nickname") .setStyle(TextInputStyle.Short), ), ) .run(async (interaction, data) => { const nickname = interaction.fields.getTextInputValue("nickname-input"); await interaction.reply(`Updated user ${data.userId} to ${nickname}`); });import editUserModal from "../../components/modals/edit-user";
await interaction.showModal( editUserModal.setData({ userId: targetUser.id }),);Modal data is stored server-side with an optional TTL — same as buttons. Expired data replies with an ephemeral error automatically.
Key points
- Handler path →
src/components/modals/ interaction.fields.getTextInputValue(id)→ read submitted text inputsinteraction.showModal(modal)→ open from a command or button.withData<T>()+.setData()→ pass typed context into the submit handler