Modal Commands
A modal command is a slash command that opens a Discord modal, a popup form with text inputs, instead of replying straight away.
Added in 2.7.0.
When to Use One
Slash command arguments are fine for one or two short values, but they are typed inline and everything is visible while you type. A modal gives you a form with up to five inputs, multi-line boxes, and placeholders, which suits feedback, bug reports, applications, and anything with a longer text body.
Creating One Interactively
botbox addPick modal as the command type. Instead of arguments you'll be asked for fields, and for each field:
- Name — the Python attribute name, no spaces or dashes, unique within the command
- Label — what Discord shows above the input, at most 45 characters
- Style —
shortfor a single line,paragraphfor a multi-line box - Required — whether Discord blocks submission when the input is empty
- Placeholder — optional grey hint text
You can add up to five fields, which is Discord's limit for a single modal.
Creating One Headlessly
Set "Type": "modal" and give a Fields array instead of Args.
botbox add Feedback --commands '[
{
"Name": "feedback",
"Scope": "guild",
"Type": "modal",
"Description": "Collects user feedback",
"Fields": [
{ "Name": "subject", "Label": "Subject", "Style": "short", "Required": true, "Placeholder": "Short summary" },
{ "Name": "details", "Label": "Details", "Style": "paragraph", "Required": false, "Placeholder": "" }
],
"ReturnType": "None"
}
]'Field object:
| Key | Type | Description |
|---|---|---|
Name | string | Python attribute name. No spaces, no dashes, unique within the command |
Label | string | Label shown in Discord, at most 45 characters |
Style | string | short or paragraph |
Required | boolean | Whether Discord requires a value before submitting |
Placeholder | string | Optional hint text, omitted from the generated code when empty |
Rules
- A modal command needs at least one field and at most five
- A modal command cannot have
Args, and a non-modal command cannot haveFields ReturnTypeis alwaysNone, BotBox overwrites whatever you pass- Modal commands are app commands, so they are stored in
slash_commandsinbotbox.conf, not in a separate list
What Gets Generated
For the example above, BotBox writes a modal class named after the command plus Modal, then a slash command that opens it.
class FeedbackModal(discord.ui.Modal, title="Collects user feedback"):
subject = discord.ui.TextInput(label="Subject", style=discord.TextStyle.short, required=True, placeholder="Short summary")
details = discord.ui.TextInput(label="Details", style=discord.TextStyle.paragraph, required=False)
async def on_submit(self, interaction: discord.Interaction):
await interaction.response.send_message(f"feedback submitted: subject={self.subject.value} details={self.details.value}", ephemeral=True)
class Feedback(commands.Cog, name="Feedback"):
def __init__(self, bot) -> None:
self.bot = bot
logger.info("feedback cog loaded")
@app_commands.command(name="feedback", description="Collects user feedback")
@app_commands.guilds(GUILD)
async def feedback(self, interaction: discord.Interaction) -> None:
await interaction.response.send_modal(FeedbackModal())The modal title comes from the command description, truncated to Discord's 45 character limit.
on_submit echoes the submitted values back as an ephemeral message. That is a placeholder so the command works the moment it is generated. Replace the body with whatever you actually want to do with the input, such as writing to a database, posting to a channel, opening a ticket, etc.Reading the Values
Every field is an attribute on the modal class, and its text is at .value.
async def on_submit(self, interaction: discord.Interaction):
subject = self.subject.value
details = self.details.value
logger.info("feedback from %s: %s", interaction.user, subject)
channel = interaction.client.get_channel(YOUR_CHANNEL_ID)
await channel.send(f"**{subject}**\n{details}")
await interaction.response.send_message("Thanks, your feedback was sent.", ephemeral=True)None. Check for "" rather than falsy-versus-missing.Config Sync
botbox config sync detects modal commands by finding a send_modal(...) call in the command body, then reads the TextInput assignments out of the modal class to rebuild the field list. Hand-written modal cogs that follow the generated shape are picked up the same way generated ones are.
botbox config sync