# esx_adminmenu (/docs/addons/esx_adminmenu)

esx_adminmenu is the official staff tool for ESX Legacy servers. It ships two interfaces: a full NUI dashboard (default `F10`) for players, bans, vehicles, logs and server settings, and a compact keyboard menu (default `F9`) for quick actions in-game. Every action is checked on the server against a configurable, per-feature permission system and written to an admin action log.

## Overview [#overview]

| | |
|---|---|
| Resource | `esx_adminmenu` |
| Side | Client + server (Svelte 5 NUI in `web/dist`) |
| Dependencies | `esx_lib`, `es_extended`, `oxmysql` |
| Optional | `esx_ambulancejob` (revive), `esx_garage` (impounds), `pma-voice` (radio), `esx_skin` (clothing menu), `esx_status` (hunger/thirst) |
| Database | Creates `bans` and `admin_logs`, extends `users` (automatic) |

Main features:

- **Players:** online list, search (including offline characters), recent players, detail panel, goto, bring, spectate, kick, ban, revive, freeze, health/armor, money, job, name, hunger/thirst, radio, routing bucket, ped model, clothing menu, give weapons, clean inventory, delete character, ACE group management.
- **Troll actions:** burn, explode, sky, random teleport, nausea (each can be disabled).
- **Vehicles:** spawner with customization (performance levels, turbo, xenon, neon, colors, tint, wheels, bulletproof tires), repair, clean, flip, delete; owned vehicle list with impound, unimpound and delete; `/admincar` to register a vehicle to yourself.
- **Server:** weather, time, blackout, PvP, population density, freeze/bring/revive/kill/kick all, delete world vehicles, peds and objects, broadcasts, give money to all.
- **Bans:** connection-time ban check across multiple identifier types, paginated ban list, expiry editing and unban.
- **Logs:** database-backed admin action log with retention, log viewer and optional Discord webhook.

## Installation [#installation]

<Steps>
<Step>
### Add the resource [#add-the-resource]

Place `esx_adminmenu` in your resources (for example inside `[esx_addons]`). The UI is prebuilt in `web/dist`; you do not need Node.js on the server.
</Step>
<Step>
### Ensure it after its dependencies [#ensure-it-after-its-dependencies]

```bash title="server.cfg"
ensure oxmysql
ensure es_extended
ensure esx_lib
ensure esx_adminmenu
```
</Step>
<Step>
### Give yourself an admin group [#give-yourself-an-admin-group]

Access depends on the ESX group (`xPlayer.getGroup()`), not on ACE. Set your group to `admin` with the ESX `setgroup` command from the server console, see [admin permissions](/docs/guides/admin-permissions).
</Step>
<Step>
### Restart and check the console [#restart-and-check-the-console]

On start the resource prints `Database tables/indexes checked/created!`. No SQL import is needed.
</Step>
</Steps>

### Database changes on start [#database-changes-on-start]

- `bans` (`id`, `identifier`, `identifiers`, `reason`, `banned_by`, `expires_at`, `banned_at`). Old bans with a bare license get the `license:` prefix added.
- `admin_logs` (`id`, `created_at`, `actor_identifier`, `actor_name`, `namespace`, `action`, `target_identifier`, `target_name`, `success`, `error`, `payload`).
- `users`: adds `created_at`, `last_seen` and `phone_number` if missing, plus a `(firstname, lastname)` search index.
- Search indexes on `owned_vehicles` and `admin_logs`.

## Permissions [#permissions]

Access is checked in three layers, all in `shared/config.lua`:

| Layer | Config key | Purpose |
|---|---|---|
| Menu access | `Config.AllowedGroups` | ESX groups that may open the menu and use its commands. Default `{ admin = true }`. |
| Feature groups | `Config.FeaturePermissions` | Per feature key, the ESX groups allowed to use it. Every key defaults to `{ admin = true }`. |
| Action mapping | `Config.AdminMenu.ActionPermissions`, `Config.PlayerActions.ActionPermissions`, `Config.ServerManagement.ActionPermissions` | Maps each concrete action to one feature key. |

Actions or feature keys without an entry are denied, so a new action never inherits broad access by accident.

### Feature keys [#feature-keys]

| Key | Grants |
|---|---|
| `selfTools` | Own noclip, godmode, invisible, revive, heal, armor, waypoint teleport |
| `playerVisibility` | Player names and blips |
| `infiniteAmmo` | Infinite ammo toggle |
| `helperCommands` | `/vec2`, `/vec3`, `/vec4`, `/heading`, `/copycoords`, `/rot`, `/model`, `/vehplate`, copy coords |
| `sensitiveInfo` | Identifiers, IP addresses and owner fields in player and vehicle data |
| `playerData` | Radio, job, name, thirst and hunger |
| `playerTeleport` | Goto and bring |
| `spectate` | Spectate a player |
| `playerNotify` | Notify a single player |
| `playerModeration` | Set health/armor, kill, revive, freeze |
| `destructive` | Clean inventory, delete character |
| `troll` | Troll actions |
| `banManagement` | View, edit and revoke bans, `/refreshbans` |
| `money` | Give and take money |
| `weapons` | Give all weapons from `Config.AdminWeapons` |
| `routingBucket` | Change routing bucket |
| `setModel` | Change ped model |
| `openClothing` | Open the clothing menu on a player |
| `acePermissions` | Add and remove ACE groups listed in `Config.AllowedAceGroups` |
| `vehicleSpawner` | Spawn vehicles |
| `vehicleTools` | Repair, clean, flip |
| `vehiclePerformance` | Tuning and visual customization |
| `vehicleManagement` | Impound and unimpound owned vehicles |
| `vehicleDestructive` | Delete vehicles |
| `vehicleOwnership` | Ownership data and `/admincar` |
| `serverManagement` | Server management tab |
| `serverEnvironment` | Weather, time, blackout, PvP, density |
| `serverPlayerModeration` | Freeze/unfreeze all, bring all, revive all |
| `serverPlayerDestructive` | Kick all, kill all |
| `serverCleanup` | Delete world vehicles, peds, objects |
| `serverBroadcast` | Notify all players |
| `serverEconomy` | Give money to all players |
| `radioLookup` | Look up a player's pma-voice radio channel |
| `logViewer` | Read the admin action log |

Example: a `mod` group that can open the menu, teleport, spectate and kick, but cannot see identifiers or touch money:

```lua title="shared/config.lua"
Config.AllowedGroups = {
	admin = true,
	mod = true,
}

Config.FeaturePermissions.playerTeleport = { admin = true, mod = true }
Config.FeaturePermissions.spectate = { admin = true, mod = true }
Config.FeaturePermissions.playerModeration = { admin = true, mod = true }
```

<Callout type="warn" title="Commands use the group list too">
The chat commands are registered with `ESX.RegisterCommand` using the groups from `Config.AllowedGroups`. Groups added there can run every command; the feature checks inside each command then decide what actually happens.
</Callout>

## Configuration [#configuration]

### General [#general]

| Key | Default | Description |
|---|---|---|
| `Config.Debug` | `false` | Extra debug prints. |
| `Config.Locale` | `"en"` | Locale. Note: hardcoded, not read from the `esx:locale` convar. |
| `Config.AllowedAceGroups` | `{ admin = true }` | ACE groups the menu may add or remove (without the `group.` prefix). |
| `Config.Keybinds.AdminMenu` | `"F9"` | Default key for `/adminmenu`. |
| `Config.Keybinds.AdminDashboard` | `"F10"` | Default key for `/admindashboard`. |

### Config.AdminMenu [#configadminmenu]

| Key | Default | Description |
|---|---|---|
| `NoClipSpeed` | `1.5` | Base noclip speed. |
| `NamesDistance` | `75.0` | Range for player name tags. |
| `ModelLoadTimeout` | `5000` | Milliseconds to wait for a model to load. |
| `ServerDataInterval` | `60000` | Refresh interval of dashboard server data (ms). |
| `Blip` | `{ sprite = 1, scale = 0.85, colour = 5 }` | Player blip style. |
| `Spectate` | `{ height = 15.0, escCooldown = 5000, streamWait = 500 }` | Spectate camera settings. |
| `Troll` | `{ skyHeight = 120.0, randomRange = 500, randomHeight = 40.0, nauseaDuration = 5000 }` | Troll action tuning. |
| `WaypointScan` | `{ max = 1000, step = 25 }` | Ground search for waypoint teleport. |
| `ActionPermissions` | see config | Self, visibility and vehicle actions mapped to feature keys. |

### Config.VehicleSpawner [#configvehiclespawner]

| Key | Default | Description |
|---|---|---|
| `DefaultModel` | `"sultan"` | Model preselected in the spawner. |
| `DeleteCurrentVehicleOnSpawn` | `true` | Delete the vehicle you sit in before spawning. |
| `WarpIntoVehicle` | `true` | Put the admin into the spawned vehicle. |
| `DefaultColor` | `"black"` | Default color preset id. |
| `ColorPresets`, `NeonPresets`, `WindowTints`, `WheelCategories`, `WheelDesigns` | lists | Options shown in the customization UI. |

### Config.ServerManagement [#configservermanagement]

| Key | Default | Description |
|---|---|---|
| `DefaultKickReason` | `"Kicked by admin"` | Reason used when none is given. |
| `DefaultWeather` | `"CLEAR"` | Initial weather. |
| `DefaultHour` / `DefaultMinute` | `12` / `0` | Initial time. |
| `DefaultBlackout` | `false` | Initial blackout state. |
| `DefaultPvp` | `true` | Initial PvP state. |
| `DefaultDensity` | `false` | Population density used when no saved override exists. `false` keeps es_extended's multipliers; or `{ pedDensity = 0.5, vehicleDensity = 0.5, randomVehicleDensity = 0.5 }`. |
| `WeatherTypes` | `CLEAR`, `EXTRASUNNY`, `CLOUDS`, `OVERCAST`, `RAIN`, `THUNDER`, `FOGGY`, `XMAS` | Weather types allowed in the UI. |
| `ActionPermissions` | see config | Server actions mapped to feature keys. |

Density changes made in the dashboard are stored in the resource KVP key `populationDensity:v1` and published through `GlobalState["esx:populationDensity"]`, so they survive restarts. This needs the matching density support in es_extended's world adjustments.

### Config.AdminLimits [#configadminlimits]

| Key | Default |
|---|---|
| `MaxReasonLength` | `180` |
| `MaxNotificationLength` | `240` |
| `MaxNotificationDuration` | `30000` |
| `MaxBanDurationMinutes` | `2628000` (5 years) |
| `MaxMoneyAmount` | `10000000` |
| `MaxRoutingBucket` | `2147483647` |
| `MaxRadioChannel` | `10000` |
| `MaxVehiclePropsBytes` | `16384` |
| `OfflineSearchResults` | `25` |
| `OfflineSearchCooldownMs` | `1000` |
| `MinOfflineSearchLength` | `3` |
| `VehiclePageSize` | `100` |
| `BanPageSize` | `100` |
| `RecentPlayersCap` | `100` |
| `PlateGenAttempts` | `50` |
| `AllowedMoneyAccounts` | `{ money = true, bank = true, black_money = true }` |
| `AllowedNotificationTypes` | `{ info = true, success = true, error = true }` |

### Other sections [#other-sections]

| Key | Default | Description |
|---|---|---|
| `Config.Ban.Identifiers` | `license`, `license2`, `discord`, `steam`, `xbl`, `live`, `fivem` = `true`; `ip` = `false` | Identifier types stored with a ban and matched on connect. A player is blocked if any identifier matches. IP is off to avoid false positives on shared IPs. |
| `Config.Plate` | `{ Letters = 3, Numbers = 3, UseSpace = true }` | Format of generated plates for `/admincar`. |
| `Config.Clothing.Events` | `{ "esx_skin:openSaveableMenu" }` | Client events fired on the target to open a clothing menu. |
| `Config.Revive.Events` | `{ "esx_ambulancejob:revive" }` | Client events sent to the target from the server on revive. |
| `Config.Revive.UseNativeFallback` | `true` | Also run a native resurrect after the events. |
| `Config.Revive.NativeFallbackDelay` | `250` | Delay (ms) before the native fallback when an event was sent. |
| `Config.AdminWeapons` | 9 weapons (pistol to nightstick) | Weapons and ammo for "give all weapons". |
| `Config.TrollActions` | `burn`, `explode`, `sky`, `randomTeleport`, `nausea` = `true` | Enable or disable each troll action. |

### Config.Logs [#configlogs]

| Key | Default | Description |
|---|---|---|
| `Enabled` | `true` | Record admin actions to `admin_logs`. |
| `RetentionDays` | `30` | Rows older than this are purged hourly. `0` keeps everything. |
| `PurgeBatchSize` | `250` | Rows deleted per purge statement. |
| `PurgeBatchDelay` | `250` | Wait (ms) between purge batches. |
| `PurgeTimeBudget` | `5000` | Max time (ms) per purge run. |
| `PurgeBacklogDelay` | `60000` | Delay (ms) before the next run when a backlog remains. |
| `FlushInterval` | `5000` | Queue flush interval (ms). |
| `MaxQueue` | `500` | In-memory queue cap; oldest entries are dropped when the database is down. |
| `BatchSize` | `100` | Rows per `INSERT`. |
| `MaxPayloadBytes` | `2048` | Larger payloads are replaced by a truncation marker. |
| `MaxPerMinutePerActor` | `120` | Per-source log rate limit, protects against spam of denied attempts. |
| `Ignore` | `noclip`, `godmode`, `invisible`, `names`, `blips`, `copyCoords` | Actions that are never logged. |
| `Webhook.Url` | `""` | Discord webhook URL. Empty disables it. |
| `Webhook.Actions` | `ban`, `unban`, `kick`, `deleteCharacter`, `cleanInventory`, `giveMoney`, `takeMoney`, `giveMoneyAll`, `kickAll`, `killAll`, `aceAdd`, `aceRemove` | Actions forwarded to the webhook. |

<Callout type="error" title="Keep the webhook secret">
Anyone with the webhook URL can post to your channel. Do not commit a real URL to a public repository.
</Callout>

## Commands [#commands]

Opening the menus:

| Command | Description |
|---|---|
| `/adminmenu` | Opens the compact admin menu (client command, key `F9`). |
| `/admin` | Opens the compact admin menu (server-checked). |
| `/admindashboard` | Opens the full dashboard (key `F10`). |
| `/information <playerId>` | Opens the dashboard on a player's detail panel. |

Moderation:

| Command | Description |
|---|---|
| `/noclip`, `/names`, `/blips` | Toggle noclip, name tags, player blips. |
| `/goto <playerId>` | Teleport to a player. |
| `/bring <playerId>` | Bring a player to you. |
| `/spectate <playerId>` / `/stopspectate` | Start or stop spectating. |
| `/kick <playerId> <reason>` | Kick a player. |
| `/ban <playerId> <duration> <reason>` | Ban a player. Duration in minutes, `0` = permanent. |
| `/notify <playerId> <message> <type> <duration> <title>` | Send a notification (`info`, `success`, `error`). |
| `/setmodel <playerId> <model>` | Change a player's ped model. |
| `/refreshbans` (`/refreshbancache`) | Reload the ban cache after editing `bans` by hand. |

Helpers (copy to clipboard, need `helperCommands`):

| Command | Copies |
|---|---|
| `/vec2`, `/vec3`, `/vec4` | Position as `vec2(...)`, `vec3(...)`, `vec4(x, y, z, heading)` |
| `/heading` | Heading |
| `/copycoords` | `x = ..., y = ..., z = ..., heading = ...` |
| `/rot` (`/rotation`) | Entity rotation as `vec3(...)` |
| `/model` (`/entitymodel`) | Model hash of your vehicle, or your ped |
| `/vehplate` (`/copyplate`) | Current vehicle plate |

`/admincar [model]` spawns the given model, or takes the vehicle you are driving, and inserts it into `owned_vehicles` with your identifier and a generated plate. It requires `vehicleOwnership`.

## Server exports [#server-exports]

### logAdminAction [#logadminaction]

```lua
exports.esx_adminmenu:logAdminAction(entry)
```

Queues an entry in the admin action log (respects `Config.Logs.Enabled`, `Ignore`, rate limit and webhook rules).

| Field | Type | Description |
|---|---|---|
| `action` | `string` | Required. Action name, max 64 chars. |
| `namespace` | `string?` | Group of the action, max 32 chars. Defaults to `"unknown"`. |
| `actor` | `number \| string` | Player source or a raw identifier. |
| `target` | `number \| string?` | Target source or identifier. |
| `actorName` / `targetName` | `string?` | Fallback names when no source is given. |
| `success` | `boolean?` | `false` marks a failed action. Default success. |
| `err` | `string?` | Error text. |
| `payload` | `table?` | Extra data, JSON encoded and truncated at `MaxPayloadBytes`. |

```lua title="server/main.lua"
exports.esx_adminmenu:logAdminAction({
    namespace = 'myjail',
    action = 'jail',
    actor = adminSource,
    target = targetSource,
    payload = { minutes = 30, reason = 'RDM' },
})
```

### getAdminLogs [#getadminlogs]

```lua
local result = exports.esx_adminmenu:getAdminLogs(filters)
```

Reads the log, newest first. All filters are optional: `search` (2-100 chars, prefix match on names, identifiers and action), `actor`, `target`, `action`, `namespace`, `days`, `limit` (1-200, default 50) and `beforeId` (cursor). Returns `{ logs, hasMore, nextCursor, limit }`.

## Events and security [#events-and-security]

- All dashboard and menu actions go through server callbacks (`esx-adminmenu:server:*`). Each one re-checks the caller's group and feature permission on the server, so a modified client cannot run admin actions.
- `esx-adminmenu:server:impoundDeletedVehicle` (server net event, `plate`) is client-triggerable but requires `vehicleDestructive`.
- `esx-adminmenu:client:*` events (`setWeather`, `setTime`, `kill`, `revive`, `troll`, and so on) are sent by the server only. Do not trigger them from other resources to bypass the permission checks and logs.
- Denied attempts are logged too, which helps spot players probing the callbacks.

## Integration tips [#integration-tips]

- **Revive with another ambulance script:** replace `Config.Revive.Events` with your script's client revive event. The native fallback still runs unless you disable it.
- **Other clothing resources:** put your resource's open-menu client event into `Config.Clothing.Events`.
- **Impounds:** with [esx_garage](/docs/addons/esx_garage) running, the vehicle tools use its `getImpounds` and `impoundVehicle` exports.
- **Hunger and thirst:** set via `esx_status:set`, so [esx_basicneeds](/docs/addons/esx_basicneeds) and esx_status must run.

## Common issues [#common-issues]

- **Menu does not open.** Your ESX group is not in `Config.AllowedGroups`. Check with `xPlayer.getGroup()`; the default `user` group has no access.
- **Button greyed out or "Insufficient Permissions".** The group is allowed to open the menu but missing in the feature key for that action.
- **Banned player still joins.** The ban was added directly in SQL. Run `/refreshbans`, and make sure the identifier has a type prefix such as `license:`.
- **Ban check fails open.** If the ban cache errors during `playerConnecting`, the player is let in and the error is printed to the console.
- **`admin_logs` grows large.** Lower `RetentionDays` or reduce what is logged with `Ignore`.

## Related [#related]

- [Admin permissions guide](/docs/guides/admin-permissions)
- [ESX commands](/docs/es_extended/commands)
- [esx_ambulancejob](/docs/addons/esx_ambulancejob)
- [esx_garage](/docs/addons/esx_garage)
- [oxmysql guide](/docs/guides/oxmysql)
