> ## Documentation Index
> Fetch the complete documentation index at: https://docs.awokenlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Categories & channels

> The in-game Category Manager, per-channel flags, and how channels are loaded.

A **category** is a chat tab. `/me`, `/global`, `/ooc`, `/staffchat`, gang chat, job chat - each is one row in the `awoken_chat_categories` DB table.

Categories are live-editable in-game via the Category Manager (wrench icon in the Settings panel, requires `awoken_chat.admin`).

## Default channel set

The 9 channels shipped out of the box:

| ID       | Command      | Proximity | Notes                                                                  |
| -------- | ------------ | --------- | ---------------------------------------------------------------------- |
| `all`    | *(none)*     | *(none)*  | Read-only merged view. Default-selected tab.                           |
| `local`  | `/local`     | 20 m      | Local chat (range-limited).                                            |
| `global` | `/global`    | *(none)*  | Server-wide.                                                           |
| `ooc`    | `/ooc`       | *(none)*  | Out of character.                                                      |
| `me`     | `/me`        | 20 m      | Action description, 3D text above the sender.                          |
| `ads`    | `/ad`        | *(none)*  | **Disabled by default** - flip Enabled on if you use it.               |
| `staff`  | `/staffchat` | *(none)*  | ACE-gated to `awoken_chat.staff`.                                      |
| `gang`   | `/gang`      | *(none)*  | Filters to `ballas,families,vagos,marabunta,lostmc` (edit the filter). |
| `job`    | `/job`       | *(none)*  | Filters to `police,sheriff,ambulance,fire,mechanic` (edit the filter). |

## Category Manager UI

Open the Settings panel, click the wrench icon. Categories list on the left, edit form on the right. Form is grouped into six sections:

| Section        | Fields                                                                             |
| -------------- | ---------------------------------------------------------------------------------- |
| **Identity**   | `id`, `label`, slash command                                                       |
| **Appearance** | colour, icon                                                                       |
| **Access**     | visibility mode, proximity, ACE permission, job filter, min job grade, gang filter |
| **3D Text**    | enable 3D, 3D-only (dependent), duration (dependent)                               |
| **Anonymity**  | anonymous, anonymous-in-webhook (dependent)                                        |
| **Behaviour**  | active, show in tabs, bypass antispam, allow reply / react actions                 |

<Tip>
  Saving an existing category keeps the form on that row (so you can iterate). Saving a brand-new category promotes the draft to `editingId` - the second save updates the row you just created instead of trying to create a duplicate.
</Tip>

## Per-channel flag reference

Every checkbox / number field in the Category Manager maps to a key in the category's JSON blob. The defaults match what most servers want.

### Identity & appearance

| Key       | Type       | Notes                                                                                                          |
| --------- | ---------- | -------------------------------------------------------------------------------------------------------------- |
| `id`      | `string`   | Unique, lowercase, `[a-z0-9_-]`. Used as the DB primary key. Cannot be changed once saved (delete + recreate). |
| `label`   | `string`   | Tab strip display name.                                                                                        |
| `color`   | `string`   | Hex `#RRGGBB`. Drives the tab tint and the message-name accent.                                                |
| `icon`    | `string`   | Font Awesome 6 Solid name, no `fa-` prefix. Picked from a curated dropdown in the UI.                          |
| `command` | `string?`  | Slash command (omit for view-only tabs like `all`).                                                            |
| `default` | `boolean?` | Mark **one** category as the default-selected tab.                                                             |

### Access

| Key          | Type                                    | Notes                                                                                                                                                  |
| ------------ | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `visibility` | `'all' \| 'sender' \| 'staff' \| 'job'` | `sender` shows the message to the sender only (useful for `/try` previews). `staff` shows only to staff + the sender. `job` requires the `job` filter. |
| `proximity`  | `number?`                               | Metres. `nil` = unlimited range.                                                                                                                       |
| `permission` | `string?`                               | ACE permission required to read/send.                                                                                                                  |
| `job`        | `string?`                               | Comma-separated jobs (QB/QBOX/ESX).                                                                                                                    |
| `grade`      | `number?`                               | Minimum job grade. Only checked when `job` is set.                                                                                                     |
| `gang`       | `string?`                               | Comma-separated gangs (QBCore/QBOX only - ESX has no native gang system).                                                                              |

### 3D text

| Key                 | Type       | Notes                                                                                                                                             |
| ------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `threeD`            | `boolean?` | Render the message body as floating text above the sender's head.                                                                                 |
| `threeDOnly`        | `boolean?` | With `threeD = true`, suppress the chat row - the 3D text is the only surface. Server still persists + webhooks the message (audit trail intact). |
| `threeDDurationSec` | `number?`  | Seconds the 3D text stays on screen. Clamped to `[0.25, 60]` client-side. `nil` = global default from `cfg.threeD.durationMs`.                    |

See [**3D text**](/resources/awoken-chat/configuration/3d-text) for the full picture (render distance, anchoring, etc.).

### Anonymity

| Key                  | Type       | Notes                                                                                                                                                                                       |
| -------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `anonymous`          | `boolean?` | Hide the sender's name in-game. The chat row reads `"Anonymous"` with a neutral silhouette avatar; senderId is stripped from the payload.                                                   |
| `anonymousInWebhook` | `boolean?` | Paired with `anonymous`. When both are true, the Discord webhook archive also reads `"Anonymous"` instead of the real character name. Default `false` = staff audit trail keeps real names. |

### Behaviour

| Key              | Type       | Notes                                                                                                                                                                                           |
| ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled`        | `boolean?` | `false` = category is fully inert. No slash command, hidden from tabs, no broadcasts. Admin can still see + re-enable in the Category Manager.                                                  |
| `showInTabs`     | `boolean?` | `false` hides from the tab strip; the slash command still works.                                                                                                                                |
| `bypassAntispam` | `boolean?` | Skip rate-limit + duplicate detection for this channel. RP-style channels like `/me` legitimately repeat ("`*nods*`", "`*sighs*`") - flip this on for those. Blocked-word filter still applies. |
| `allowReplies`   | `boolean?` | `false` hides the Reply action on messages. Default `true`.                                                                                                                                     |
| `allowReactions` | `boolean?` | `false` hides the React action. Default `true`.                                                                                                                                                 |

## Creating a custom channel

Example: a `/confess` confessional channel that posts anonymously in-game AND in Discord, with 3D text disabled (it's a chat-only channel), and the same proximity as `/local`.

1. Wrench icon → **New category**
2. Fill in:
   * **ID**: `confess`
   * **Label**: `Confessions`
   * **Slash command**: `confess`
   * **Color**: `#A78BFA`
   * **Icon**: `comment`
3. Access: **Proximity 20**
4. Anonymity: tick **Anonymous** + tick **Anonymous in webhook**
5. Save

The `/confess` command is bound at save time (no restart needed). If `webhooks.channels.confess` is set in `config.lua`, every message also posts to that Discord channel, with the author block reading `Anonymous`.

## Live edits & runtime updates

| What changed                                     | Effect                                                                                          | Restart needed?                                             |
| ------------------------------------------------ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| Created a new category                           | Tab + slash command + suggestion picker entry appear for every connected client                 | No                                                          |
| Renamed a category's command (`/me` → `/action`) | New command is bound immediately. Old command keeps working as a free alias until next restart. | No                                                          |
| Toggled Enabled off                              | `byId` lookup returns nil → handler responds `unknown_channel` → tab disappears                 | No                                                          |
| Toggled Enabled on                               | Tab + command re-bind                                                                           | No                                                          |
| Changed perms / job / gang filter                | Next `visibleFor()` call (admin save broadcasts one immediately) re-runs the check              | No                                                          |
| Deleted a category                               | Tab disappears. Old slash command stays registered but the handler returns `unknown_channel`.   | Until next restart for the dead command to go away entirely |

## Job & gang filtering

Filters are simple comma-separated case-insensitive matches against the framework's job/gang name fields.

```text theme={"dark"}
Job filter:   police,sheriff,ambulance,fire
Gang filter:  ballas,families,vagos
```

The bridge resolves the player's current job/gang and runs a lower-case match. There's no role abstraction - if your server uses non-standard ids like `metro-police`, add them explicitly.

<Tip>
  After granting a player a new job via your framework, the relevant tab appears automatically within \~1s - the bridge listens to `qbx_core:server:onJobUpdate` / `QBCore:Server:OnJobUpdate` / `esx:setJob` and pushes a refreshed channels list.
</Tip>

## Resetting to defaults

```sql theme={"dark"}
TRUNCATE awoken_chat_categories;
```

Then `restart awoken_chat`. The seed pass re-inserts the 9 default channels.

<Warning>
  This nukes any **custom** categories. Export anything bespoke first.
</Warning>
