> For the complete documentation index, see [llms.txt](https://mango-5m.gitbook.io/mango5m-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://mango-5m.gitbook.io/mango5m-docs/resources/mango-admin/configuration/bans-and-identifiers.md).

# Bans and Identifiers

What a ban is written against, banning by several identifiers at once, and keeping bans in your own database.

A ban is only as good as what it was written against. One license is replaced in a minute — so every ban records **everything the server can see about that player**, and is enforced against all of it.

{% hint style="success" %}
**Player tokens are the strongest part.** They belong to the copy of the game rather than to a license, so somebody who buys a second license and comes back is still refused.
{% endhint %}

## What a ban is written against

| Identifier                                                | Matched by default |
| --------------------------------------------------------- | ------------------ |
| **License**                                               | Yes                |
| **License 2** — the Rockstar (ROS) license                | Yes                |
| **FiveM / Cfx.re ID** — also called the Cfx.re Account ID | Yes                |
| **Discord**                                               | Yes                |
| **Steam** — stored as the hex id and as steam64           | Yes                |
| **Player tokens**                                         | Yes                |
| **IP address**                                            | No — recorded only |
| **Endpoint**                                              | No — recorded only |
| **Server ID** — also called Player ID or Net ID           | No — recorded only |
| **Routing bucket**                                        | No — recorded only |
| **Player name**                                           | No — recorded only |
| **Steam name**, **Discord username**                      | No — recorded only |

Everything in that list is written onto the ban either way, so a ban always shows what was known at the time. The right-hand column only decides what is allowed to **catch** somebody.

{% code title="shared/config/moderation.lua" %}

```lua
Config.BanIdentifiers = {
    license = true,  license2 = true,  fivem = true,
    discord = true,  steam    = true,  tokens = true,

    ip       = false,  -- households share one, and most change every few days
    name     = false,  -- the player types it
    endpoint = false,  -- the port is different every session

    announce = true,   -- print what is matched, and what cannot be collected, on start
}
```

{% endcode %}

### What cannot be collected

{% hint style="warning" %}
FiveM sends a server six identifier types: **license, license2, fivem, discord, steam and ip**. Anything else has to come from somewhere else, or not at all.

* **Rockstar ID** and **Rockstar name** are never sent. `license2` **is** the Rockstar (ROS) license, and it is recorded and matched — that is as close as the platform allows.
* **FiveM username** is never sent; only the numeric Cfx.re account id, which is recorded.
* **Xbox** and **Microsoft** gamertags are not sent, and there is no lookup for them.
* **Steam** and **Discord usernames** are not sent either, but the panel can look them up at the moment of the ban if you have given it a Steam API key or a Discord bot token.
* **Discord** and **Steam** ids only arrive if the player is running Discord, or launched through Steam.

With `announce` on, the console prints all of this on every start, so what your bans cover is never a guess.
{% endhint %}

## Banning without the player online

The **Ban** page takes as many identifiers as you have — one per line.

```
license:1a2b3c...
discord:281974...
steam:110000112345678
```

Every one of them is enforced. A bare 40-character license hash is understood on its own; anything else needs its prefix, and the panel says so rather than guessing what you meant.

{% hint style="info" %}
**If one of those identifiers belongs to somebody who is online**, the panel bans them properly instead — reading their tokens and everything else from the live connection — and keeps the extra identifiers you typed as well.
{% endhint %}

### Adding identifiers later

Nobody has the full picture on the day. Press **Identifiers** on any ban to see what it currently catches, add more, or take off one that was mistyped.

The ban keeps its number and its reason — it simply catches more from then on. A ban cannot be emptied to nothing this way; to let somebody back in, unban them.

## Banning from another resource

Anything else on your server that decides somebody has to go — an anticheat, a jail script, your own commands — can put them out **through the panel** rather than beside it. The ban gets the same record, the same disconnect screen, the same identifiers and the same audit line as one made by a staff member.

{% code title="anywhere on your server" %}

```lua
-- a connected player: everything about them is read before they are dropped
exports.mango_admin:CreateBan(source, {
    reason = 'Aimbot detected',
    days   = 14,              -- 0 or missing = permanent. seconds and expires work too
    by     = 'My Anticheat',  -- what the record says. Defaults to the calling resource
})

-- or by identifier, with anything else you happen to know about them
exports.mango_admin:CreateBan('license:1a2b3c...', {
    reason      = 'Ban evasion',
    identifiers = { 'discord:281974...' },
})

exports.mango_admin:CreateKick(source, { reason = 'Go and cool off' })

-- lifting one again: a ban id, the number staff quote, or an identifier
exports.mango_admin:RemoveBan(42, { by = 'Appeals bot' })
exports.mango_admin:RemoveBan('license:1a2b3c...')
```

{% endcode %}

Both answer with a table you can log or show:

```lua
{ ok = true,  id = '1726...', num = 42, name = 'Cheater', expires = 0, identifiers = 7 }
{ ok = false, error = 'You cannot ban an owner' }
```

{% hint style="info" %}
**An identifier belonging to somebody who is connected is banned properly**, not as an offline ban — the panel reads their tokens and the rest from the live connection first, and keeps whatever identifiers you passed as well.

An identifier can be on more than one ban, and `RemoveBan` lifts all of them — it answers with `removed`, so nothing reports success while leaving one in force. Owners cannot be banned this way either. The same two are available as server events, `mango_admin:createBan` and `mango_admin:createKick`, which are deliberately not net events — a client can never ask for a ban.
{% endhint %}

## Bans in your database

By default bans live inside the resource. Turning this on keeps them in your own database as well, where they survive the folder being replaced and can be shared by more than one server.

{% code title="shared/config/moderation.lua" %}

```lua
Config.BanDatabase = {
    enabled      = true,
    table        = 'mango_admin_bans',
    idTable      = 'mango_admin_ban_ids',
    createTables = true,   -- created on first use
    readOnStart  = true,   -- load what the database holds into the panel's list
    syncOnStart  = true,   -- and push up anything the database does not have yet
}
```

{% endcode %}

Needs `oxmysql`. Two tables are created the first time it runs — the bans, and the identifiers each one is enforced against — and nothing else in your database is touched.

Lifting a ban marks it as lifted rather than deleting it, so the record and the reason survive the unban.

{% hint style="warning" %}
**Sharing one ban table across two servers:** licenses, Rockstar licenses, Cfx.re ids, Discord and Steam all match anywhere. **Player tokens do not** — they are specific to the server that issued them, so on the second server a ban falls back to the other identifiers.
{% endhint %}

## If the panel and the guard are both stopped

While the panel is running, it checks every connection. While the panel is stopped, [the guard](/mango5m-docs/resources/mango-admin/resource-protection.md) holds the door in its place. If **both** are stopped, nothing is running to refuse anybody.

A ban kept in your database can be enforced by something else entirely, and `mango_admin/tools/ban_enforcer.lua` is included for exactly that.

{% stepper %}
{% step %}

### Turn the database on

`Config.BanDatabase.enabled = true`.
{% endstep %}

{% step %}

### Add it to a resource you would never stop

Paste the contents of `tools/ban_enforcer.lua` into a **server** file of your framework core — `es_extended`, `qb-core`, `ox_core`, or your own base resource.

Do not make it a resource of its own: a resource can be stopped, which is the situation it is there to cover.
{% endstep %}

{% step %}

### Match the table names

Only if you changed them:

```lua
local BAN_TABLE    = 'mango_admin_bans'
local BAN_ID_TABLE = 'mango_admin_ban_ids'
```

{% endstep %}
{% endstepper %}

While the panel is up, both check the same connection and reach the same answer; the player is refused once.

{% hint style="info" %}
**The guard can read the ban table too.** It learns the table names from the panel, so there is normally nothing to set. If the panel has never started on a boot, name them in the guard's own config:

```lua
Config.Hold.BanDatabase = { table = 'mango_admin_bans', idTable = 'mango_admin_ban_ids' }
```

{% endhint %}
