Skip to main content

Modals

Modals are defined via .setModal() on the builder and triggered by buttons with opensModal set. When the user submits the modal, the onSubmit callback fires and the menu re-renders automatically.

The builder field accepts a standard Discord.js ModalBuilder — refer to the discord.js documentation for supported components and builder configuration.

PropertyTypeRequiredDescription
builderModalBuilderYesDiscord.js modal builder with text inputs configured
onSubmit(ctx, fields) => Awaitable<void>NoCallback fired on submission
idstringNoRequired when the menu has more than one modal

Single modal

When a menu has one modal, omit id and set opensModal: true on the trigger button:

import { ModalBuilder } from 'discord.js';

.setModal(() => ({
builder: new ModalBuilder()
.setTitle('Submit Feedback')
.addComponents(/* ... */),
onSubmit: async (ctx, fields) => {
const feedback = fields.getTextInputValue('feedback');
ctx.state.set('feedback', feedback);
},
}))
.setButtons(() => [
{
label: 'Open Form',
style: ButtonStyle.Primary,
opensModal: true,
},
])

Multiple modals

When a menu has more than one modal, give each an id and use that ID in opensModal on the trigger button:

.setModal(() => [
{
id: 'edit-name',
builder: new ModalBuilder()
.setTitle('Edit Name')
.addComponents(/* ... */),
onSubmit: async (ctx, fields) => {
ctx.state.set('name', fields.getTextInputValue('name'));
},
},
{
id: 'edit-bio',
builder: new ModalBuilder()
.setTitle('Edit Bio')
.addComponents(/* ... */),
onSubmit: async (ctx, fields) => {
ctx.state.set('bio', fields.getTextInputValue('bio'));
},
},
])
.setButtons(() => [
{ label: 'Edit Name', style: ButtonStyle.Secondary, opensModal: 'edit-name' },
{ label: 'Edit Bio', style: ButtonStyle.Secondary, opensModal: 'edit-bio' },
])

The onSubmit callback

onSubmit: (ctx: MenuContext, fields: ModalSubmitFields) =>
Awaitable<void>;

fields is Discord.js's ModalSubmitFields — refer to the discord.js documentation for the full access API. Example:

onSubmit: async (ctx, fields) => {
const value = fields.getTextInputValue('your-input-custom-id');
ctx.state.set('result', value);
// Menu re-renders automatically after onSubmit completes
},

Button configuration

See Buttons — Modal trigger buttons for the opensModal property on button configs.

note

opensModal buttons must not be deferred before the modal is shown. FlowCord handles this automatically — do not set action alongside opensModal expecting both to run. If both are present, opensModal takes precedence.