> For the complete documentation index, see [llms.txt](https://docs.tworst.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tworst.com/getting-started-installation/admin-menu/admin-menu/configuration.md).

# Configuration

The Admin Menu is configured through four files in the `shared/` folder:

| File                        | Purpose                                                             |
| --------------------------- | ------------------------------------------------------------------- |
| `shared/config.lua`         | Framework, appearance, access, economy, integrations, features      |
| `shared/server_config.lua`  | API keys, exports, Live View, Tworst Cloud uplink                   |
| `shared/webhooks.lua`       | Discord webhook logging routes                                      |
| `shared/open_functions.lua` | Integration hooks (vehicle keys, spectate, noclip, custom clothing) |

{% hint style="info" %}
Defaults are sensible — in most cases you only need to set your framework, locale, god access, the Discord token, and your webhooks to get started.
{% endhint %}

***

## shared/config.lua

### Framework & General

```lua
TW.Framework = "QBCore"   -- "QBCore" or "ESX"
TW.Locale = "en"          -- en, tr, de, fr, es, pt, ru, pl, nl, it, ro, ar, th
TW.AutoSql = true         -- Auto-create/migrate database tables on start
```

| Setting        | What it does                                                                                                                                                                  |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TW.Framework` | Your server framework. Use `"QBCore"` for **both QBCore and QBox** (QBox is QBCore-compatible), or `"ESX"` for ESX.                                                           |
| `TW.Locale`    | Panel language. 13 languages are built in: en, tr, de, fr, es, pt, ru, pl, nl, it, ro, ar, th (ไทย). Want another? See [Adding a Custom Language](#adding-a-custom-language). |
| `TW.AutoSql`   | When `true`, all database tables are created/migrated automatically on start. Set `false` only if you import `sql/install.sql` manually.                                      |

### Adding a Custom Language

The panel ships with 13 built-in languages. You can add your own **without editing the UI or rebuilding** — just two files inside the resource's `locales/` folder.

#### Step 1 — Create the translation file

Copy `locales/en.json` and rename it to your language code (ISO 639-1, e.g. `hu` for Hungarian):

```
locales/hu.json
```

Open it and translate the **values only** — never change the keys (the text on the left):

```json
{
  "header.hi": "Szia,",
  "header.onDuty": "Szolgálatban",
  "header.offDuty": "Szolgálaton kívül"
}
```

{% hint style="info" %}
Keep every key. Any value you leave in English simply shows in English — nothing breaks.
{% endhint %}

#### Step 2 — Register the language

Open `locales/custom_languages.json` and add an entry to the list so it appears in the picker:

```json
[
  { "code": "hu", "name": "Magyar" }
]
```

* `code` → must match your JSON filename (`hu` → `hu.json`)
* `name` → the label shown in the language menu (write it in the language itself)

Add more the same way:

```json
[
  { "code": "hu", "name": "Magyar" },
  { "code": "cs", "name": "Čeština" }
]
```

{% hint style="warning" %}

* **Don't list the built-in languages here** — they're always available.
* Only add a language whose `<code>.json` file exists, otherwise it shows up empty (falls back to English).
* Lines starting with `//` in that file are comments and are ignored.
  {% endhint %}

#### Step 3 — Apply

Restart `tw-adminmenu`, then open **Panel → Settings → Language** — your language is now selectable.

### Appearance

```lua
TW.DefaultTheme        = "dark"      -- "dark" | "gray" | "light"
TW.DefaultPrimaryColor = "#4189e5"   -- accent color (hex)
```

The **default** theme and accent color applied the first time an admin opens the panel. If an admin later changes them in **Settings → Appearance**, their personal choice (saved in their browser) overrides these defaults.

### Onboarding

```lua
TW.Onboarding = { enabled = true }
```

A short first-open setup (language + theme) and feature tour, shown **once per admin**. Set `enabled = false` to disable it entirely (everyone is treated as already onboarded).

### Access & Duty

```lua
TW.GodRole       = "owner"     -- role assigned to god identifiers
TW.GodIdentifiers = { }        -- 'fivem:xxx' / 'discord:xxx' get full access automatically
TW.DefaultOnDuty = false       -- admins join off-duty (recommended)
TW.OffDutyAccess = "full"      -- "full" | "readonly" | "duty"
```

| Setting             | What it does                                                                                                                                    |
| ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `TW.GodRole`        | The top-tier role granted to god identifiers.                                                                                                   |
| `TW.GodIdentifiers` | Identifiers that always get full access on join — your owners/devs.                                                                             |
| `TW.DefaultOnDuty`  | `false` = admins must turn duty on manually (recommended); `true` = on-duty the moment they connect.                                            |
| `TW.OffDutyAccess`  | What an off-duty admin can do — `full` (everything, just shows offline), `readonly` (view only, no actions), or `duty` (nothing until on-duty). |

See [Permissions & Roles](/getting-started-installation/admin-menu/admin-menu/permissions.md) for the full role system.

### Whitelist

```lua
TW.WhitelistDefaultEnabled = false
TW.WhitelistKickReason = "This server is in whitelist mode. You are not authorized to join."
```

| Setting                      | What it does                                                                                                       |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `TW.WhitelistDefaultEnabled` | Whether whitelist mode is active on server boot. The in-game toggle is one-shot — a restart reverts to this value. |
| `TW.WhitelistKickReason`     | Message shown to non-whitelisted players when they're blocked at connect.                                          |

### Player Display

```lua
TW.PlayerNameMode = "fivem"        -- "fivem" | "character"
TW.HandcuffAllowWalk = true
TW.NotifyTargetOnAction = true
```

| Setting                   | What it does                                                                                                                        |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `TW.PlayerNameMode`       | How names show on the Online Players page — `character` (character name first, FiveM name in parentheses) or `fivem` (the reverse). |
| `TW.HandcuffAllowWalk`    | `true` = handcuffed players can walk slowly (no sprint/attack); `false` = fully locked in place.                                    |
| `TW.NotifyTargetOnAction` | `true` = the target player gets a pop-up when an admin acts on them (heal, freeze, jail, etc.); `false` = silent admin actions.     |

### Avatars & Phone

```lua
TW.AvatarSource = "discord"        -- "discord" | "steam" | "custom" | "none"
TW.PhoneSource = "metadata"        -- "metadata" | "custom" | "none"
TW.PhoneMetadataKey = "phone"
```

| Setting               | What it does                                                                                                                                                                                            |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `TW.AvatarSource`     | Where player avatars come from. The chosen source is tried first, then falls back (e.g. `discord` → steam → placeholder). `discord`/`steam` need the matching API key/token set in `server_config.lua`. |
| `TW.AvatarCustom`     | Only used when `AvatarSource = "custom"` — an SQL table/column to read an avatar URL from.                                                                                                              |
| `TW.PhoneSource`      | How the player's phone number is resolved — `metadata` (reads framework metadata, works for ESX/qb-phone), `custom` (SQL lookup for gksphone/lb-phone/etc.), or `none` (hidden).                        |
| `TW.PhoneMetadataKey` | The metadata key the phone number is stored under (default `phone`).                                                                                                                                    |

***

### Economy

The economy block tells the panel **how your server stores money** so that balances display correctly and "set money" actions write to the right place. Configure it to match your framework/inventory.

```lua
TW.Currency = "$"   -- "$" | "€" | "₺" | "£" | "¥" | "₽" | "₹"
```

`TW.Currency` is just the symbol shown before every amount across the UI (PlayerDetail, RichList, invoices, set-money, etc.).

#### Cash & Dirty Money

```lua
TW.Cash = {
    mode = "account",                 -- "account" | "item" | "disabled"
    accountNames = { "cash", "money" },
    itemName = "cash",
}

TW.DirtyMoney = {
    mode = "account",
    accountNames = { "crypto", "black_money" },
    itemName = "black_money",
}
```

**Choosing `mode`:**

| Mode       | When to use it                                                                                                                                                             |
| ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `account`  | **Most servers.** Money is a standard framework account (QBCore `PlayerData.money`, ESX `accounts`). This is the default.                                                  |
| `item`     | Your server tracks money as an **inventory item** (e.g. ox\_inventory item-as-cash, Renewed-Cash, marked bills). The panel reads/writes the item count via your inventory. |
| `disabled` | Hide this money type from the panel entirely.                                                                                                                              |

**`accountNames` (for `mode = "account"`):** the panel uses the **first matching name** in the list. Defaults cover QBCore and ESX out of the box (`cash`/`money`, `crypto`/`black_money`). If your server renamed the account, **put your custom name first** — e.g. `accountNames = { "dirty_money", "black_money" }`.

**`itemName` (for `mode = "item"`):** the inventory item name to read/write. Works automatically with whichever inventory system is active.

{% hint style="info" %}
**Quick guide:** Standard QBCore/ESX server? Leave everything on `account` — it just works. Using an item-based cash system? Switch `mode = "item"` and set `itemName`. Don't use dirty money at all? Set `TW.DirtyMoney.mode = "disabled"`.
{% endhint %}

***

### Integration Bridges (Auto-Detected)

Compatibility bridges for your other resources. Leave each on `"auto"` to detect the running script automatically, or pin an exact key to force a specific provider.

```lua
TW.InventorySystem        = "auto"   -- qb-inventory | ox_inventory | qs-inventory | codem-inventory | tgiann-inventory | origen_inventory | core_inventory | ps-inventory | chezza-inventory | esx
TW.WeatherSystem          = "auto"
TW.VehicleKeysSystem      = "auto"
TW.GarageSystem           = "auto"
TW.InvoicesSystem         = "auto"   -- qb-phone | g-billing | esx_billing | codem-billing | codem-billingv2 | codem-mphone | wasabi_billing | bablo-billing
TW.FuelSystem             = "auto"
TW.JailSystem             = "auto"
TW.ClothingSystem         = "auto"
TW.CommunityServiceSystem = "savana-communityservice"
```

{% hint style="warning" %}
For `"auto"` to work, the integration resource must start **before** `tw-adminmenu` in your `server.cfg`. If it starts after, pin the exact key here instead. (This is why `CommunityServiceSystem` is pinned by default — auto-detect would otherwise miss it.)
{% endhint %}

See the [supported systems table](/getting-started-installation/admin-menu/admin-menu.md#supported-systems) for every detectable provider.

### Garage, Dimensions & Dashboard

```lua
TW.GarageConfig = {
    defaultGarage = "pillboxgarage",   -- where "Send to Garage" puts vehicles
    impoundLot    = "impoundlot",      -- where "Impound" sends vehicles
    depotPrice    = 500,               -- impound retrieval fee
    perSystem = { --[[ per-garage-script overrides ]] },
}
```

The garage IDs **must match what your garage script actually uses**, or admin "Send to Garage / Impound" actions will assign vehicles to a garage that doesn't exist. The flat values are fallbacks; `perSystem` overrides them for specific garage scripts. Copy the real IDs from your garage script's own config if you renamed them.

```lua
TW.DimensionPresets = {
    { label = "Default World", value = 0 },
    { label = "Admin World",  value = 999 },
    { label = "Staff Area",   value = 500 },
}
```

Named routing buckets (dimensions) selectable in the player actions menu — handy for private staff areas or events.

```lua
TW.Dashboard = {
    serverJoinUrl = "cfx.re/join/abc123",   -- shown on the dashboard
    enableSystemMetrics = true,             -- CPU/RAM/tick monitoring
    historyRetentionDays = 30,              -- how long metric history is kept
    pushIntervalMs = 5000,                  -- how often live stats refresh
}
```

### Tickets, Chat, Noclip, Replay, Troll

```lua
TW.Chat.retentionDays = 7              -- how long chat history is stored
TW.Tickets.maxActivePerPlayer = 3      -- max open tickets a player can have

TW.Noclip.Speeds = { 15.0, 45.0, 100.0, 220.0, 450.0 }   -- noclip speed tiers (m/s)
TW.Noclip.SnapToGroundOnExit = true    -- drop to ground when leaving noclip

TW.Replay = {
    enabled = true,
    durationSec = 8,                   -- clip length
    cooldownSec = 90,                  -- min time between auto-clips
    autoCapture = true,                -- auto-record on triggers below
    triggers = { shooting = true, death = true },
}

TW.Troll.LocalSpawns = true            -- spawn troll entities only on the target's screen (anticheat-safe)
TW.Troll.SpawnTimeoutMs = 30000        -- auto-delete spawned entities after 30s
```

***

## shared/server\_config.lua

### API Keys

```lua
TW_Server.DiscordBotToken = ""   -- enables Discord avatars + member/role lookups
TW_Server.SteamApiKey     = ""   -- enables Steam avatars/profile data
```

{% hint style="warning" %}
Set `DiscordBotToken` for Discord avatars and role/member info to work. The token must belong to a bot that is a member of your Discord server. (This is separate from the standalone Discord bot — see [Discord Bot](/getting-started-installation/admin-menu/admin-menu/discord-bot.md).)
{% endhint %}

### File Exports

```lua
TW_Server.exportPublicHost = "http://YOUR_SERVER_IP:30120"
TW_Server.exportTtlSec     = 300       -- download link validity (seconds)
TW_Server.exportViaCloud   = true      -- serve exports via Tworst cloud CDN
```

Controls the temporary download links the panel generates for data exports (player data, logs, etc.).

### Mugshots & Voice

```lua
TW_Server.mugshotWebhook     = ""      -- Discord webhook for mugshots
TW_Server.ticketVoiceWebhook = ""      -- Discord webhook for ticket voice notes
```

### Live View (WebRTC Screen Watching)

```lua
TW_Server.liveViewDefaultPreset = "medium"   -- low | medium | high | extra | source
TW_Server.liveViewAudio = {
    enabled = true,
    radiusMeters = 15.0,                      -- proximity voice mix radius
}
```

`liveViewDefaultPreset` sets the starting stream quality (480p → native 60fps). `liveViewAudio` mixes in nearby in-game voices so you can hear what's happening around the player you're watching.

### Auto Web Permissions

```lua
-- Kept ABOVE the Cloud block on purpose so re-pairing (which replaces the Cloud
-- block) never wipes this setting.
TW_Server.autoWebPermissions = false   -- default false (opt-in)
```

When `true`, granting someone **in-game** staff (who has a Discord identifier) also gives them **web-panel access** with the same role — and editing/removing their in-game staff mirrors to the web. The link key is the Discord ID, and it's self-healing (existing admins are back-filled on their next connection). Manual web-only grants you added by hand in the panel are never touched. See [Web Panel → Invite Your Staff](/getting-started-installation/admin-menu/admin-menu/web-panel.md#step-3-invite-your-staff).

### Tworst Cloud Uplink

```lua
TW_Server.Cloud = {
    enabled = false,                          -- enable remote web panel access
    url = "wss://panel.tworst.com/agent",
    tenantId = "",                            -- from the panel's pairing snippet
    serverId = "",
    secret = "",
}
```

{% hint style="info" %}
Get `tenantId`, `serverId`, and `secret` from the pairing snippet in the web panel. Full steps in [Web Panel (Tworst Cloud)](/getting-started-installation/admin-menu/admin-menu/web-panel.md).
{% endhint %}

***

## shared/webhooks.lua

Route in-game admin logs to Discord. **Fill a webhook for every category you want logged** — anything blank falls back to `default`, and if `default` is also blank those logs go nowhere.

```lua
TW_Webhooks.enabled = true
TW_Webhooks.botName = "TW Admin Logs"
TW_Webhooks.botAvatar = ""

TW_Webhooks.urls = {
    punishments = "",   -- kicks, warns, jails, bans, CS, blacklist
    player      = "",   -- heal, freeze, teleport, model changes
    inventory   = "",   -- item give/remove, metadata edits
    economy     = "",   -- money changes
    vehicles    = "",   -- spawn, delete, repair, mods
    staff       = "",   -- role changes, job assignments, duty toggles
    server      = "",   -- exports, dashboard actions
    world       = "",   -- weather, time, freeze
    tickets     = "",   -- ticket create/claim/close
    ticketsOpen = "",   -- (optional) ticket OPEN events only
    ticketsClose= "",   -- (optional) ticket CLOSE events only (with transcript link)
    spectate    = "",   -- Live View, screenshots, spectate
    notes       = "",   -- player/vehicle notes
    troll       = "",   -- troll menu actions
    security    = "",   -- denied RPC / permission failures
    default     = "",   -- catch-all fallback
}
```

{% hint style="danger" %}
Leaving webhooks empty means those actions won't be logged. At minimum, set the `default` webhook so nothing is lost — then add dedicated channels for the categories you care most about (e.g. `punishments`, `economy`, `security`).
{% endhint %}

{% hint style="success" %}
Logs are posted as rich Discord embeds with color coding, timestamps, and actor/target details.
{% endhint %}

{% hint style="info" %}
**Ticket logging (no bot required):** Ticket open & close events — including a transcript link on close — post straight to Discord via these webhooks. Leave everything under `tickets` for one channel, or split them into two channels with `ticketsOpen` and `ticketsClose`.
{% endhint %}

***

## shared/open\_functions.lua

This file holds **integration hooks** — small functions that run at key moments so you can wire the Admin Menu into your server's other systems. Edit the function bodies; leave the signatures as they are.

### `AfterVehicleSpawn(vehicle, plate)`

Runs on the client right after an admin spawns a vehicle. Use it to hand the vehicle keys to your key system (or any post-spawn logic).

```lua
function TW.OpenFunctions.AfterVehicleSpawn(vehicle, plate)
    -- qb-vehiclekeys:
    TriggerEvent('vehiclekeys:client:SetOwner', plate)
    -- qs-vehiclekeys:   exports['qs-vehiclekeys']:GiveKeys(plate)
    -- wasabi_carlock:   exports.wasabi_carlock:GiveKey(plate)
end
```

### `OnSpectateStart(targetServerId)` / `OnSpectateStop(targetServerId)`

Run on the spectating admin's machine when spectate starts/stops — e.g. to hide a HUD or notify scripts while spectating.

```lua
function TW.OpenFunctions.OnSpectateStart(targetServerId)
    TriggerEvent('myhud:disable')
end
function TW.OpenFunctions.OnSpectateStop(targetServerId)
    TriggerEvent('myhud:enable')
end
```

### `OnNoclipStart()` / `OnNoclipStop()`

Run on the admin's machine when noclip is toggled — useful for whitelisting them in your anticheat or bypassing collision/zone scripts.

```lua
function TW.OpenFunctions.OnNoclipStart()
    exports['my-anticheat']:Whitelist(true)
end
function TW.OpenFunctions.OnNoclipStop()
    exports['my-anticheat']:Whitelist(false)
end
```

### `OpenClothingMenu(ped)`

Only used when `TW.ClothingSystem = "custom"`. Called when the Quick Action **Clothing** button is clicked, so you can open your own appearance script. (In the built-in modes — illenium/fivem/qb/esx — the bridge handles this and the hook is ignored.)

```lua
function TW.OpenFunctions.OpenClothingMenu(ped)
    exports.rcore_clothing:openClothing()
end
```

{% hint style="info" %}
The same spectate/noclip state can be read by other resources via exports/events — see [Features → Exports & Events](/getting-started-installation/admin-menu/admin-menu/features.md#exports-events).
{% endhint %}
