How it works
Setup, the ticket lifecycle, every variable, every command, and what to do when Discord behaves in a way that looks like a bug.
Setup
-
Sign in and add the bot
Signing in reads who you are and which servers you hold Manage Server in. That is Discord's own permission, read live on every load, so the list is exactly what you can already administer — and someone demoted this morning loses access this afternoon rather than whenever a cache expires.
-
Create a panel
A panel is a ticket type. It owns the message people see, the button they press, the form they fill in, the category tickets land in, and who can handle them. A server can have as many as it likes, and one posted message can carry up to 25 of them.
-
Post it
Run
/panelin the channel you want it in. Posting again updates the existing message in place rather than leaving a trail of dead panels. -
Name your support team
Under Roles and access. Almost every other screen refers back to this list, so it is worth doing before anything else is tuned.
Ticket lifecycle
created → opened → closed → re-opened → deleted, with claimed and unclaimed running alongside as a separate axis.
Closed and deleted are deliberately different things. Closing denies the opener view of the channel, renames it and moves it to the closed category — the history is intact and it can be re-opened. Deleting removes the channel. The ticket record survives deletion on purpose: statistics, the audit trail and any transcript already taken all reference it, and removing the row along with the channel would silently rewrite last month's numbers.
Variables
Usable in any message, embed, channel name or custom command. Chain modifiers with a pipe:
{owner.username|upper|max:12}.
An unknown variable renders as nothing, never as its own name. A typo
that printed {owner.usrname} into a live support channel would look like the
bot is broken; printing nothing looks like a field that was not filled in. Substitution
is also single-pass, so a value that itself contains braces is not re-scanned — which is
what stops someone typing a role ping into a ticket reason and having it expand.
Panel editor
Every tab below is per panel, so a billing ticket and a bug report can look and behave nothing alike.
Messages
Thirteen points where the bot says something, each with an embed builder. Leave one alone and it uses the built-in wording.
| Message | When it is sent |
|---|---|
| Panel message | The panel itself, in a public channel |
| Ticket message | First message inside a new ticket |
| Created DM / Closed DM | Direct message to the opener, if DMs are on |
| Close question | The are-you-sure confirmation |
| Opened embed | Posted when a ticket is re-opened |
| Closed embed | Posted when a ticket is closed |
| Moderator embed | In the staff thread, where the opener cannot see it |
| Claim / Unclaim | When staff take or release a ticket |
| Transcript message | Alongside a delivered transcript |
| Delete message | Posted before deletion, so it lands in the transcript |
| Escalate message | When a ticket is handed to another panel |
All accept the variables above. A mention only pings from the message text, never from inside an embed, which is why each editor has a box for both.
Channel permissions
Nine targets, each a grid of allow, inherit and deny across twenty-two permissions. Inherit keeps whatever the role has server-wide. This is what makes a ticket private, and why the bot never asks for Administrator, which would bypass every row.
| Target | Applies |
|---|---|
| @everyone | Separately while open and while closed |
| Ticket owner | Separately while open and while closed |
| Support team | Separately while open and while closed |
| Additional roles | Separately while open and while closed |
| Claimed override | Layered on top once somebody claims it |
Untouched means the built-in private defaults. One rule cannot be switched off: unless you explicitly allow @everyone to view, it stays denied.
Automation and custom commands
Automation rules
A trigger and a list of actions, written as JSON. Seven of the ten triggers fire the moment they happen: created, closed, re-opened, claimed, unclaimed, close requested and claim requested.
{"window":3600,"source":"owner","fireOn":"absence","actions":[{"type":"close","delay":0}]}
Actions: close, delete, sendCommand,
stopAutomations, startAutomations. The last two let a human take
over a conversation a rule was about to close.
Custom commands
Run one with /custom. Three modes: strict matches the whole
text, wildcard understands * and ?,
regex puts named groups into the response. Strict beats wildcard beats regex.
{"trigger":"faq","match":"strict","response":"See {server.name} docs.","delivery":"channel"}
Delivery is channel, dm or ephemeral. Text after the
trigger becomes {answer.1} onwards, up to five.
Permissions
Discord shows an invite's permissions as a list with no explanation, and never reviews what an app asks for. The only gate is an admin pressing Authorize. So here is the whole list with a reason for each.
Commands
| Command | Does | Who |
|---|---|---|
/ticket [reason] | Opens a ticket | Anyone not blacklisted |
/claim | Takes ownership of the ticket | Support team |
/close [reason] | Closes it, keeping the history | Support team, or the opener |
/panel [name] | Posts a panel into this channel | Manage Server |
/ask [question] | Answers from this server's documentation, citing the page, or hands you to a human | Anyone |
/custom [text] | Runs one of this server's custom commands | Anyone |
/unclaim | Releases a claim so somebody else can take it | The claimer, or an admin |
/escalate | Hands the ticket to the panel its type escalates to | Support team |
/ping | Confirms the bot is awake | Anyone |
/add [user] | Gives somebody access to this ticket | Support team |
/remove [user] | Takes an individual grant away again | Support team |
/move [panel] | Hands the ticket to another panel and re-files the channel: new category, new team's access, new name | Support team |
/bulk [action] [inactive_days] | Closes or deletes many tickets at once. Reports what it would take and does nothing until you confirm that number | Manage Server, or an admin role |
/note [text] | Writes a private note on this ticket. The person who opened it never sees it | Support team |
/notes | Reads the private notes back | Support team |
/rename [name] | Renames the ticket's channel | Support team |
/transfer [user] | Hands the claim to another staff member | Support team |
/thread | Opens a staff-only thread, kept out of the transcript | Support team |
/closerequest [reason] | Asks the opener to confirm it can be closed | Support team |
/open | Re-opens a closed ticket | Support team |
/delete | Deletes the channel for good | Support team |
/transcript | Produces a transcript now | Support team |
/priority [level] | Sets urgency, which decides the ticket's place in the queue | Support team |
/credits | Shows your credit balance and lets you top it up. Credit belongs to you, not to a server, so it works anywhere and spends anywhere | Anyone |
/boost | Moves this ticket up the queue, if the server offers it | Anyone in the ticket |
/link [provider] | Proves you own a Steam, Roblox or Twitch account | Anyone |
/unlink [provider] | Disconnects a linked account | Anyone |
/quicksetup | Creates a working category and panel in one step | Manage Server |
Two more live on Discord's right-click menu rather than the slash list: Open a ticket about this on any message, and Open a ticket with this user on any member.
When it goes wrong
A slash command does not appear
Global commands take up to an hour to propagate, which looks exactly like a bug and has cost people entire afternoons. Register to a single server while testing and it appears instantly.
"I do not have permission to create channels here"
The bot's role is missing Manage Channels, or a category overwrite is denying it. A server admin can edit the bot's auto-created role, but only within their own role hierarchy — they cannot grant what they do not hold themselves.
The bot's banner keeps disappearing
Discord's developer portal never loads an existing banner back into its upload control, so every Save on that page submits the field empty and deletes it — including saves for something else entirely. Set the banner from the dashboard, which sends only the fields you actually named.
Sign-in returns "invalid_state"
The sign-in did not start on this page, or it sat unfinished for more than fifteen minutes. Start it again from the dashboard. On your own application, it also means the redirect URL registered in the portal does not match exactly — a trailing slash is a different URL to Discord.