Skip to main content

Getting Started

Discord Ticket Bot

A modern, self-hosted Discord ticket bot built on Discord.js v14 and SQLite — no external database, no telemetry, full feature set out of the box.


✨ Features

FeatureDescription
🎫 Ticket TypesUp to 25 configurable types with individual emoji, color, category & questions
📋 QuestionnairesModal forms (up to 5 questions) shown when opening a ticket
🙋 Claim SystemStaff can claim/unclaim — button toggles, embed & topic update automatically
🔴 PrioritiesLow / Medium / High / Urgent via /priority — shown in channel topic & embed
📝 Staff NotesPrivate notes via /note add / /note list
🔀 Move TicketMove to a different type/category via /move or button (staff only)
🛡️ Type-specific Staff RolesEach ticket type can define its own staff roles
🖼️ Panel Logo & BannerOptional logo thumbnail and/or banner image in the panel embed
🎛️ Panel Interaction TypeChoose between a Button or a direct Select Menu in the panel
⭐ Rating System1–5 star feedback after closing, automatically posted to a configured channel
⏰ Staff ReminderAutomatic ping inside the ticket if no staff responds within X hours
⏰ Auto-CloseAutomatically close inactive tickets with a configurable warning period
📄 HTML TranscriptFull HTML transcript sent to log channel and creator via DM
📊 StatisticsServer-wide stats and detailed per-user stats via /stats
🚫 Blacklist/blacklist add/remove/list to block users from opening tickets
🌍 MultilingualGerman and English included, easily extensible
🗄️ SQLiteNo external database required — file is created automatically

📁 Project Structure

discord_ticketbot/
├── index.js # Entry point
├── package.json
├── .env.example # Environment variable template
├── ticketbot.service # systemd unit file for Linux servers
├── assets/ # Static files (logo, banner images)
│ ├── logo.png # Panel logo thumbnail (place your own here)
│ └── banner.png # Panel banner image (place your own here)
├── config/
│ └── config.example.jsonc # Configuration template (with comments)
├── locales/
│ ├── de.json # German
│ └── en.json # English
├── data/
│ └── tickets.db # SQLite database (auto-created)
└── src/
├── client.js # Extended Discord Client
├── config.js # Config loader & validation
├── database.js # All DB operations (SQLite)
├── handlers/
│ ├── commandHandler.js # Loads & registers slash commands
│ ├── eventHandler.js # Loads Discord events
│ └── componentHandler.js # Loads buttons, modals, menus
├── commands/ # Slash commands
│ ├── setup.js # /setup – Send panel
│ ├── close.js # /close – Close ticket
│ ├── add.js # /add – Add user
│ ├── remove.js # /remove – Remove user
│ ├── claim.js # /claim – Claim ticket
│ ├── unclaim.js # /unclaim – Unclaim ticket
│ ├── move.js # /move – Move ticket
│ ├── rename.js # /rename – Rename channel
│ ├── transcript.js # /transcript – HTML transcript
│ ├── priority.js # /priority – Set priority (topic + embed)
│ ├── note.js # /note – Staff notes
│ ├── blacklist.js # /blacklist – Block users
│ └── stats.js # /stats – Statistics (server & user)
├── events/
│ ├── ready.js # Bot start, status, auto-close & staff reminder loop
│ ├── messageCreate.js # Track last activity
│ └── interactionCreate.js # Route all interactions
├── components/
│ ├── buttons/
│ │ ├── openTicket.js # tb_open
│ │ ├── closeTicket.js # tb_close
│ │ ├── claimTicket.js # tb_claim
│ │ ├── unclaimTicket.js # tb_unclaim
│ │ ├── moveTicket.js # tb_move (opens type selection)
│ │ ├── deleteTicket.js # tb_delete (confirmation step)
│ │ ├── deleteConfirm.js # tb_deleteConfirm
│ │ ├── deleteCancel.js # tb_deleteCancel
│ │ └── rateTicket.js # tb_rate:N
│ ├── modals/
│ │ ├── closeReason.js # tb_modalClose
│ │ └── ticketQuestions.js # tb_modalQuestions:type
│ └── menus/
│ ├── panelSelect.js # tb_panelSelect (SELECT_MENU mode)
│ ├── ticketType.js # tb_selectType (BUTTON mode, ephemeral)
│ └── moveSelect.js # tb_moveSelect
└── utils/
├── logger.js # Coloured console logger
├── embeds.js # All embed constructors
├── transcript.js # HTML transcript generator
└── ticketActions.js # Core logic: openTicket, performClose, performMove, refreshTicketMessage

⚙️ Slash Commands

CommandPermissionDescription
/setupAdministratorSend the ticket panel
/close [reason]ConfigurableClose the current ticket
/claimStaffClaim a ticket — updates topic & embed, button toggles to Unclaim
/unclaimStaffRelease a claimed ticket — updates topic & embed, button toggles back
/moveStaffMove ticket to a different type/category
/add <user>StaffAdd a user to the ticket
/remove <user>StaffRemove a user from the ticket
/rename <n>StaffRename the ticket channel
/transcriptStaffGenerate an HTML transcript
/priority <level>StaffSet ticket priority (updates channel topic & embed)
/note add <text>StaffAdd a staff note
/note listStaffList all notes for this ticket
/statsStaffServer-wide ticket statistics
/stats @userStaffDetailed statistics for a specific user
/blacklist addManage GuildBlock a user
/blacklist removeManage GuildUnblock a user
/blacklist listManage GuildShow the blacklist

🔘 Ticket Buttons

Every ticket channel contains a button row at the top:

ButtonVisible whenDescription
🔒 Close TicketAlways (configurable)Disables all buttons, generates transcript, closes & renames channel
🙋 ClaimclaimButton: true, not yet claimedStaff claims — topic & embed update, button becomes Unclaim
🙌 UnclaimclaimButton: true, already claimedStaff releases — topic & embed update, button becomes Claim
🔀 MoveMore than 1 ticket typeStaff opens type selection (staff only)
🗑️ Delete TicketAfter closingDeletes the channel after confirmation

🗄️ Database Schema

The SQLite database is created automatically at data/tickets.db. Existing databases are automatically migrated if columns are missing.

TableContents
ticketsAll tickets: status, type, priority, claim info, reminder, transcript
blacklistBlocked users with reason and timestamp
staff_notesPrivate staff notes per ticket
ratingsRatings (1–5 ⭐) with optional comment

🌍 Adding a New Language

  1. Copy locales/de.json, e.g. as locales/fr.json
  2. Translate all strings
  3. Set "lang": "fr" in config/config.jsonc

📝 License

AGPL-3.0 — Source code must remain open and be published under the same license when distributed or hosted.