Ticket form
A support system in three steps: a message with a button, a modal with the form and a ticket summary.
-
The panel. A pinned message in the support channel:
Preview payload.json {"flags": 32768,"components": [{"type": 17,"accent_color": 5793266,"components": [{"type": 9,"components": [{"type": 10,"content": "## 🎫 Support\nHaving a problem? Open a ticket and we will help you."}],"accessory": {"type": 2,"style": 1,"label": "Open ticket","custom_id": "open_ticket"}}]}]} -
The modal. When the button is clicked, the bot responds with this modal:
Preview New ticketCategory*Pick a categoryDescribe the problem*Screenshots (optional)Drag files or click to uploadmodal.json {"type": 9,"data": {"custom_id": "ticket_modal","title": "New ticket","components": [{"type": 18,"label": "Category","component": {"type": 3,"custom_id": "category","placeholder": "Pick a category","options": [{"label": "Technical issue","value": "technical","emoji": {"name": "🛠️"}},{"label": "Report a user","value": "report","emoji": {"name": "🚨"}},{"label": "Something else","value": "other","emoji": {"name": "💬"}}]}},{"type": 18,"label": "Describe the problem","component": {"type": 4,"custom_id": "description","style": 2,"min_length": 20,"max_length": 1500}},{"type": 18,"label": "Screenshots (optional)","component": {"type": 19,"custom_id": "screenshots","required": false,"min_values": 0,"max_values": 3}}]}} -
The summary. When the modal is submitted, the bot creates the ticket and replies with a summary:
Preview payload.json {"flags": 32768,"components": [{"type": 17,"accent_color": 16705372,"components": [{"type": 10,"content": "## Ticket #0042 · 🛠️ Technical issue"},{"type": 10,"content": "-# Opened by <@123456789012345678> <t:1793620800:R>"},{"type": 14},{"type": 10,"content": "> The bot hasn't answered the /profile command since yesterday."},{"type": 1,"components": [{"type": 2,"style": 3,"label": "Claim","custom_id": "ticket_claim:42"},{"type": 2,"style": 4,"label": "Close","custom_id": "ticket_close:42"}]}]}]}
- When you receive
custom_id: "open_ticket", respond with the modal JSON (type: 9). - When you receive the
MODAL_SUBMITwithcustom_id: "ticket_modal", read the values and respond withtype: 4and the summary.
client.on(Events.InteractionCreate, async (interaction) => { // Step 2: open the modal if (interaction.isButton() && interaction.customId === 'open_ticket') { await interaction.showModal(ticketModal); // the modal's "data" object from above return; }
// Step 3: receive the form if (interaction.isModalSubmit() && interaction.customId === 'ticket_modal') { const category = interaction.fields.getStringSelectValues('category')[0]; const description = interaction.fields.getTextInputValue('description');
const summary = new ContainerBuilder() .setAccentColor(0xfee75c) .addTextDisplayComponents( (text) => text.setContent(`## Ticket · ${category}`), (text) => text.setContent(`-# Opened by ${interaction.user}`), ) .addSeparatorComponents((separator) => separator) .addTextDisplayComponents((text) => text.setContent(`> ${description}`));
await interaction.reply({ flags: MessageFlags.IsComponentsV2, components: [summary] }); }});class TicketModal(discord.ui.Modal, title="New ticket"): category = discord.ui.Label( text="Category", component=discord.ui.Select( placeholder="Pick a category", options=[ discord.SelectOption(label="Technical issue", value="technical", emoji="🛠️"), discord.SelectOption(label="Report a user", value="report", emoji="🚨"), discord.SelectOption(label="Something else", value="other", emoji="💬"), ], ), ) description = discord.ui.Label( text="Describe the problem", component=discord.ui.TextInput(style=discord.TextStyle.paragraph, min_length=20, max_length=1500), ) screenshots = discord.ui.Label( text="Screenshots (optional)", component=discord.ui.FileUpload(required=False, min_values=0, max_values=3), )
async def on_submit(self, interaction: discord.Interaction): view = discord.ui.LayoutView() view.add_item( discord.ui.Container( discord.ui.TextDisplay(f"## Ticket · {self.category.component.values[0]}"), discord.ui.TextDisplay(f"-# Opened by {interaction.user.mention}"), discord.ui.Separator(), discord.ui.TextDisplay(f"> {self.description.component.value}"), accent_colour=0xFEE75C, ) ) await interaction.response.send_message(view=view)
# In the "open_ticket" button callback:# await interaction.response.send_modal(TicketModal())s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { switch i.Type { case discordgo.InteractionMessageComponent: if i.MessageComponentData().CustomID == "open_ticket" { s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseModal, Data: ticketModalData(), // returns *discordgo.InteractionResponseData with the modal }) }
case discordgo.InteractionModalSubmit: data := i.ModalSubmitData() if data.CustomID != "ticket_modal" { return } var category, description string for _, c := range data.Components { label, ok := c.(*discordgo.Label) if !ok { continue } switch input := label.Component.(type) { case *discordgo.SelectMenu: if len(input.Values) > 0 { category = input.Values[0] } case *discordgo.TextInput: description = input.Value } }
color := 0xFEE75C s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Flags: discordgo.MessageFlagsIsComponentsV2, Components: []discordgo.MessageComponent{ discordgo.Container{ AccentColor: &color, Components: []discordgo.MessageComponent{ discordgo.TextDisplay{Content: "## Ticket · " + category}, discordgo.Separator{}, discordgo.TextDisplay{Content: "> " + description}, }, }, }, }, }) }})