# Ticket system

> Post a panel with an "Open a ticket" button. Each click opens a private thread for the member and your staff.

You will build a support ticket system:

1. A panel message with an **Open a ticket** button.
2. A click opens a **private thread**. Only the member and your staff can see it.
3. Inside, a **Close ticket** button deletes the thread when the problem is solved.

Works on every plan.

## What you need

- A channel for the panel, for example `#support`.
- The bot needs these permissions in that channel: **Create Private Threads**, **Send Messages in Threads** and **Manage Threads**.
- Staff need **Manage Threads** to see every ticket.

## How the parts fit

| Part | Kind | Job |
|---|---|---|
| `/ticket-panel` | Slash command | Posts the panel once |
| `Tickets` | Event: Button Click | Runs when someone clicks a ticket button |

The panel button stays on the message for weeks. Each click runs the event. The event knows which button was clicked by its **custom ID**, a short name you give each button.

## 1. The panel command

Open **Create → Slash Command**.

| Field | Value |
|---|---|
| Command Name | `ticket-panel` |
| Description | `Post the ticket panel` |

Open the permissions part of the form. In **Discord permissions**, pick **Manage Server**. Only staff see the command.

Under **Behavior**, turn on **Only the user sees the reply**.

Pre-hook:

```python
panel = ComponentBuilder()
panel.add_button("Open a ticket", style="primary", emoji="🎫", custom_id="ticket_open")

send_message(
    channel,
    message=Message(
        title="Need help?",
        description="Press the button below. A private ticket opens for you and our staff.",
        color="#5865F2",
    ),
    components=panel,
)

set_content("Panel posted.")
```

## 2. The button event

Open **Create → Event**.

| Field | Value |
|---|---|
| Name | `Tickets` |
| Type | `Button Click / Select Menu Change` |
| Custom ids | `ticket_open, ticket_close` |

Code:

```python
# Answer Discord first. It waits only 3 seconds.
defer_response(ephemeral=True)

if custom_id == "ticket_open":
    thread = create_thread(
        channel,
        name=f"ticket-{user.username}",
        type="private",
        initial_message=f"{user.mention} thanks for opening a ticket. Tell us how we can help.",
    )

    close = ComponentBuilder()
    close.add_button("Close ticket", style="danger", custom_id="ticket_close")
    send_message(thread, "Staff: press this when the ticket is solved.", components=close)

    respond_interaction(interaction, f"Your ticket is open: {thread.mention}")

elif custom_id == "ticket_close":
    if not has_permission(user, "manage_threads"):
        respond_interaction(interaction, "Only staff can close tickets.")
    else:
        delete_channel(channel)
```

What it does:

- `defer_response(ephemeral=True)` tells Discord "working on it". The answer only shows to the clicker.
- `create_thread(..., type="private")` makes the private thread. Mentioning the member in `initial_message` adds them to it.
- The **Close ticket** button has its own custom ID, so the same event handles it.
- `delete_channel(channel)` deletes the thread. Here `channel` is the thread the button is in.

> **Watch out:** Each custom ID must be in the event's **Custom ids** box. A button whose ID is not listed does nothing, and Discord shows "This interaction failed".

## 3. Test it

1. Run `/ticket-panel` in `#support`.
2. Press **Open a ticket**. A link to your new thread appears.
3. Open the thread and press **Close ticket**. The thread is deleted.
4. Try **Close ticket** from an account without **Manage Threads**. It says only staff can close.

Errors from your code show in **Logs → Error Logs** on the dashboard.

## Ideas

- **Ask why first.** Open a form when the button is clicked, then make the thread with the answer. See [Forms](https://ccbot.app/docs/concepts/forms).
- **Log closed tickets.** Before `delete_channel`, post `send_message("ticket-log", f"{channel.name} closed by {user.mention}")`.
- **One ticket per member.** Save the open thread ID with `update_member_var("ticket", thread.id)` and check it before making a new one. See [Storage](https://ccbot.app/docs/reference/storage).
- **Stop spam clicks.** In a button event, `wait = cooldown("1 minute", key=user.id)` gives the time left, or `False` when the member may click. Stop with a message when `wait` is set.
