This is a fork of discord-tickets/bot by eartharoid, still GPL-3.0-or-later and still recognisably the same bot. Anything not described here behaves as upstream does and is documented at https://discordtickets.app.
This page is the full list. The README links here rather than repeating it.
| What it does | Upstream | |
|---|---|---|
| Thread tickets | Tickets as threads in one channel, so a server is not limited to 50 open tickets | — |
| Forum tickets | Tickets as forum posts, for public support | — |
| Backup categories | Overflow into another category when a Discord category is full | — |
| Auto-assign | The first staff member to reply claims the ticket | — |
| Close with a reason | A button, not just the slash command | — |
| Ticket priorities | High / medium / low, shown in the channel name | Partial |
| Channel-name emoji | Configurable per state and priority, per category | — |
| Waiting-on-staff status | Tracks whether a ticket is waiting on staff or on its author | — |
| Automations | A visual rule builder: 15 triggers, 13 actions, conditions, cron | — |
| Panels | Multiple ticket panels per server, edited visually | Partial |
| Block editor | Compose Discord Components V2 messages without JSON | — |
| Settings dashboard | In-repo, not a separate deployment | Separate repo |
| Member portal | Members see their own tickets; staff get a queue | — |
| Feedback portal | Every rating and comment, filterable | Partial |
| HTML transcripts | Rendered transcripts with a searchable archive | Text only |
| Bot customisation | Per-server bot name, avatar, banner and bio | — |
| Inheritable settings | Server-wide defaults each category can override | — |
| Temporal | Scheduled and durable work survives restarts | In-process timers |
| Encryption at rest | Required, not optional | Optional |
"Partial" means upstream has something in the area, but not in this form.
Upstream creates one channel per ticket. Discord caps a category at 50 channels, which is the practical ceiling on how many tickets a server can have open.
A category here picks one of three modes:
- Channel — one channel per ticket, as upstream.
- Thread — a thread in a single parent channel. No category limit, and the channel list stays readable.
- Forum — a post in a forum channel. Suited to public support, where the point is that other members can read the answers.
Thread and forum tickets have no total limit, because the limit exists to avoid the 50-channel cap and it does not apply to them.
When a channel-mode category's Discord category is full, the ticket is created in a nominated backup category instead of failing. The backup is validated to belong to the same server.
Per category. The first staff member to send a message in an unclaimed ticket claims it, so tickets stop sitting unowned while someone is already answering.
Alongside the slash command there is a close-with-reason button, an optional close request the ticket author can accept, and a configurable reopen window during which a closed ticket can be brought back. The window is durable — see Temporal.
Tickets have a priority, and the channel name carries a prefix built from the ticket's state and priority. Which emoji those are is configurable per category with a server-wide default behind it, and the prefix is rebuilt by a single idempotent function rather than by string surgery at each call site.
An automation or /emoji can also pin a specific emoji to one ticket.
A ticket records whether it is waiting on staff or on its author, flipped by whoever sent the last message. It drives the staff queue and, optionally, an emoji in the channel name — debounced, because Discord allows two channel renames per ten minutes.
A visual rule builder in the dashboard, with no counterpart upstream. Rules are graphs of nodes, validated before they are saved and executed with a bounded runtime.
- 15 triggers — ticket created, claimed, released, closed, reopened, moved, stale, feedback received; message created; button pressed; menu selected; member joined or left; bot command; cron schedule.
- 13 actions — send, reply, DM, ephemeral reply, react; add or remove a role; claim, close, move or rename a ticket; write to the log; run another automation.
- Conditions for branching, an HTTP action for outbound calls, and cron triggers backed by Temporal schedules rather than an in-process timer.
Runs are recorded, so a rule that misfires can be inspected after the fact.
Upstream ships its settings UI as a separate project. Here it lives in src/dashboard/ and is served by the bot, so there is one thing to deploy and one version to keep straight. It is built during install and shipped prebuilt in releases.
A server can have several ticket panels, each edited visually and re-sent to Discord from the dashboard. Panel and opening messages are composed in a block editor that produces Discord Components V2 layouts — containers, sections, buttons, select menus, separators — with a live preview, rather than requiring hand-written JSON.
The member-facing side. Members see the tickets they have opened; staff get a queue of what needs attention — unclaimed, never answered, gone quiet, or waiting on staff — without needing the admin permissions the settings panel requires.
Ratings and comments collected on close, with a filterable view: date range, category, rating distribution and trend over time.
Per-server bot username, avatar, banner and bio, so one instance serving several communities can present differently in each.
Most category settings can be left unset and inherited from a server-wide
default: channel name, limits, cooldown, slowmode, staff/ping/required/blocked
roles, and the emoji settings. null means inherit and an empty value means a
deliberate "none", and the two are kept distinct all the way down.
Upstream produces text transcripts on request. This fork also renders HTML transcripts — message content, embeds, components, attachments and replies — and gives admins a searchable archive of them in the dashboard.
Where those bytes live is pluggable. The database holds only a reference of the
form local:transcripts/ticket-<id>.html, never the HTML. The default driver
writes to <data dir>/user/transcripts; an S3-compatible driver is available
for deployments that need it, and is opt-in rather than assumed. Either way the
transcript is served through the dashboard's own admin-checked route under a
strict CSP, never handed out as a presigned URL. A transcript that has gone
missing is regenerated from the archived messages on next view, so storage is a
cache rather than a system of record. See "Transcript storage" in
docs/installation.md.
ENCRYPTION_KEY is required and the bot refuses to start without it. Ticket
topics, close reasons, feedback comments and archived messages are encrypted at
rest. It is not recoverable: losing it means losing that data, so it must be set
deliberately and never changed.
A server's configuration and tickets can be exported to an archive and imported into another instance. Both run as durable jobs, so a large export survives a restart, and the importer validates against an explicit allow-list rather than trusting the archive.
Every scheduled or long-running job — stale-ticket handling, auto-close, the
reopen window, deferred channel renames, transcript generation, import/export,
cron automations — runs on Temporal instead of
setTimeout.
This is not a preference. In-process timers are lost on restart, and a deploy inside a ticket's stale window silently cancels it; with more than one process every timer fires. Durable workflows survive restarts and are idempotent.
The cost is a hard dependency: Temporal is required, and the bot exits at startup without it. The provided docker-compose.yml brings up a cluster.
SQLite support has been removed. DB_PROVIDER must be mysql or
postgresql. Both are kept in step — the schema and every migration exist for
each, and the test suite checks they agree.
Migrations run automatically at startup, and a failed migration stops the boot rather than letting the bot run against a half-migrated schema.
Node.js 20+ (22 LTS recommended) on glibc. Alpine and musl are not supported, because Temporal's native addon has no musl build.
| SQLite | Removed. Use MySQL or PostgreSQL. |
| Optional encryption | ENCRYPTION_KEY is now required. |
| In-process scheduling | Replaced by Temporal, which is a required dependency. |
| Separate dashboard deployment | Merged into this repo and served by the bot. |
| Alpine / musl images | Unsupported. |
Upstream configuration that this fork does not change is still documented at https://discordtickets.app. What it does add or change is in docs/installation.md, with src/env.js as the authoritative validation.
See MIGRATING.md. It covers each install method, moving off SQLite, and recovering a half-applied migration.