Complete Guide — Support Ticket System
Building a private support system on Discord requires orchestrating channel creation, explicit permission configuration, interactive components (Discord buttons), and clean deletion upon closing.
[!CAUTION] Deprecated legacy functions: Do not use
$newTicketor$closeTicketfor production bots. These legacy helpers create public channels without access restrictions and do not reliably clean up text channels. Exclusively use the proven modular pattern documented below.
1. Ticket System Architecture
To ensure total confidentiality for conversations between the user and staff:
[ Parent Category: 🔒 Private Support ] <── @everyone: View Channel = DENIED
│
├── [ Ticket Channel: #ticket-username ]
│ ├── Staff & Bot (Inherited permissions: View, Send, Manage)
│ ├── Ticket Creator (Explicit permission: View, Send, History)
│ │
│ └── [ Welcome message with Embed & Red Button "Close Ticket" ]
- Private Parent Category: Set up a Discord category where
@everyonehasView Channeldisabled. The bot and moderation roles are granted administrative or management rights. - Channel Creation: When
/ticketexecutes, create a text channel nested under this category. - Explicit Permissions: Apply an explicit permission overwrite for the member who triggered the command (
allow: 68608= View Channel + Send Messages + Read Message History). - Welcome Message & Button: Immediately send a welcome embed into the channel with an interactive button (
customId: close_ticket, Danger / Red style). - Ephemeral Confirmation: Reply to the slash command with an ephemeral message (visible only to the author) containing a clickable channel link
<#ID>. - Closing & Deletion: When the button is clicked, acknowledge the interaction, wait 3 seconds for confirmation, then permanently delete the channel.
2. Step 1: /ticket Command (Creation & Welcome)
/ticket on the server.BDScript code executed by the engine:
;; 1. Create text channel under the private category
$var[ticketChan;$createChannel[ticket-$username;text;123456789012345678]]
;; 2. Grant permissions to creator (+viewchannel, +sendmessages, +readmessagehistory)
$editChannelPerms[$var[ticketChan];$authorID;+viewchannel;+sendmessages;+readmessagehistory]
;; 3. Send welcome embed with close button to the new channel
$useChannel[$var[ticketChan]]
$title[Support & Help]
$description[Hello <@$authorID>! Please describe your issue here.\nA support team member will assist you shortly.\n\nTo close this ticket, click the red button below.]
$color[#5865F2]
$addButton[no;close_ticket;Close Ticket;danger]
;; 4. Reset channel and acknowledge the slash command ephemerally (no double $sendMessage!)
$useChannel[]
$ephemeral
✅ Your support ticket has been created: <#$var[ticketChan]>
Discord Visual Preview: Immediate Confirmation
Here is what the user immediately sees in the channel where they ran /ticket:
Discord Visual Preview: Dedicated Private Channel
Here is what is posted inside the #ticket-jeremy channel:
3. Step 2: Closing the Ticket (Button Click or /ticket-close)
When a member or moderator clicks the Close Ticket button (customId: close_ticket), Discord emits the interactionCreate event.
close_ticket is clicked.BDScript interaction handler:
;; 1. Immediately acknowledge the click to prevent the Discord 3s timeout
🔒 Close requested by $username. Deleting this channel in 3 seconds...
;; 2. Clean wait timer
$wait[3s]
;; 3. Permanently delete the ticket channel
$deleteChannels[$channelID]
Discord Visual Preview: Closure Notification
4. Complete JSON Representation (Runtime Action Engine)
Here is the exact JSON structure stored in the database and executed sequentially by the native Dart runtime:
Ticket Creation
[
{
"type": "createChannel",
"key": "ticket_chan",
"enabled": true,
"payload": {
"name": "ticket-((user.username))",
"type": "text",
"categoryId": "123456789012345678"
}
},
{
"type": "editChannelPermissions",
"depend_on": ["ticket_chan"],
"enabled": true,
"payload": {
"channelId": "((action.ticket_chan))",
"targetType": "member",
"targetId": "((user.id))",
"allow": "68608",
"deny": "0"
}
},
{
"type": "sendMessage",
"depend_on": ["ticket_chan"],
"enabled": true,
"payload": {
"channelId": "((action.ticket_chan))",
"content": "Hello <@((user.id))>!",
"embeds": [
{
"title": "🎫 Support & Help",
"description": "Welcome to your private support channel. Please describe your issue below.",
"color": "#5865F2"
}
],
"components": {
"items": [
{
"type": "actionRow",
"components": [
{
"type": "button",
"customId": "close_ticket",
"label": "Close Ticket",
"style": 4
}
]
}
]
}
}
},
{
"type": "respondWithMessage",
"depend_on": ["ticket_chan"],
"enabled": true,
"payload": {
"content": "✅ Your support ticket has been created: <#((action.ticket_chan))>",
"ephemeral": true
}
}
]
Ticket Closing
[
{
"type": "respondWithMessage",
"enabled": true,
"payload": {
"content": "🔒 Close requested. Deleting this channel in 3 seconds...",
"ephemeral": false
}
},
{
"type": "wait",
"enabled": true,
"payload": {
"duration": "3s"
}
},
{
"type": "removeChannel",
"enabled": true,
"payload": {
"channelId": "((channel.id))"
}
}
]
5. Lightweight Alternative: Tickets via Private Threads
If your server is approaching Discord’s 500-channel limit, or if you prefer keeping history organized without creating entire text channels, you can use Private Threads:
- Create a general ticket landing channel
#support-tickets. - When
/ticketis run, invoke thecreateThreadaction withtype: "privateThread". - Add the user with
addThreadMember. - To archive and close the ticket without destroying it, use
updateChannelwitharchived: trueandlocked: true.