# esx_multicharacter (/docs/core/esx_multicharacter)

`esx_multicharacter` lets one FiveM account own several ESX characters. Players pick, create or delete a character on a selection screen before they spawn, and admins can grant extra slots or lock characters. ESX enables multicharacter mode automatically as soon as this resource is present on the server.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_multicharacter` |
| Version | 1.16.3 (fxmanifest, ships with ESX Legacy 1.16.4) |
| Side | Client + server, React NUI |
| Dependencies | `es_extended`, `esx_context`, [esx_identity](/docs/core/esx_identity), [esx_skin](/docs/core/esx_skin), `esx_lib`, `oxmysql` |
| Requirements | OneSync enabled (not `off` or `legacy`), server artifact 10188 or newer |

`es_extended` sets `Config.Multichar = GetResourceState("esx_multicharacter") ~= "missing"`. You do not toggle multicharacter in the es_extended config; you add or remove this resource.

## How characters are stored [#how-characters-are-stored]

Every character is a normal row in `users`. Its identifier is `<Config.Prefix><slot>:<license>`, for example `char1:1a2b3c...`. The player keeps one license; the prefix selects the character.

On start the resource migrates the database itself:

- adds `users.disabled` (`TINYINT(1)`, default `0`) used by `/disablechar`
- creates the `multicharacter_slots` table (`identifier`, `slots`)
- scans every table with an `identifier` or `owner` `VARCHAR` column, widens it to fit the prefix and adds an index

The scanned tables are also the ones cleaned when a character is deleted, so you do not need to register your own tables for deletion as long as they use an `identifier` or `owner` column.

<Callout type="warn">
If the migration fails, the console prints `[esx_multicharacter] 1.16.0 migration failed; character selection is disabled` and players hang on a black screen. Check that your database user can run `ALTER TABLE` and `CREATE INDEX`.
</Callout>

## Configuration [#configuration]

`config.lua` is shared, but some keys only exist on one side.

| Key | Side | Default | Description |
| --- | --- | --- | --- |
| `Config.Locale` | Shared | `GetConvar("esx:locale", "en")` | UI language. |
| `Config.CanDelete` | Shared | `true` | Allows players to delete their own characters. |
| `Config.Relog` | Shared | `true` | Registers the `/relog` command. |
| `Config.Slots` | Server | `3` | Default slot count for every player. The code falls back to `4` if you remove the key. |
| `Config.Prefix` | Server | `"char"` | Identifier prefix. Keep it short; changing it on a live server orphans existing characters. |
| `Config.Spawn` | Client | `{ { x = -284.2856, y = 562.4627, z = 172.9182, w = 19.9895 } }` | List of camera positions for the selection scene. One is picked at random. |
| `Config.Default` | Client | `{ m = {...}, f = {...} }` | Default skinchanger appearance for new male and female characters. |

<Callout type="info">
`Config.Spawn` is only the selection scene. The spawn point of a new character is the default value of the `position` column in the `users` table.
</Callout>

## Commands [#commands]

| Command | Permission | Arguments | Description |
| --- | --- | --- | --- |
| `/relog` | everyone | none | Logs out the current character and returns to selection. Only when `Config.Relog = true`. Rate limited. |
| `/setslots` | `admin` | `identifier` (string), `slots` (number) | Sets a custom slot count. |
| `/remslots` | `admin` | `identifier` (string) | Removes the custom slot count, so the player falls back to `Config.Slots`. |
| `/enablechar` | `admin` | `identifier` (string), `charslot` (number) | Re-enables a disabled character. |
| `/disablechar` | `admin` | `identifier` (string), `charslot` (number) | Locks a character so it cannot be selected. |

The admin commands are allowed from the server console. The database change is applied, but the confirmation notification then throws a script error because there is no xPlayer, so prefer running them in game. `identifier` is the raw license (without `char1:`), for example:

```txt title="In-game chat"
/setslots 1a2b3c4d5e6f7a8b9c0d 5
/disablechar 1a2b3c4d5e6f7a8b9c0d 2
```

The slot count is clamped between `1` and `100`.

## Events and callbacks [#events-and-callbacks]

### Server [#server]

| Name | Type | Description |
| --- | --- | --- |
| `esx_multicharacter:SetupCharacters` | Net event (client to server) | Loads the player's characters and sends `esx_multicharacter:SetupUI`. Rate limited to one request per 5 seconds. |
| `esx_multicharacter:CharacterChosen` | Net event | `(charid, isNew)`. Selects an existing slot or reserves an empty one for registration. |
| `esx_multicharacter:DeleteCharacter` | Net event | `(charid)`. Deletes a character if `Config.CanDelete` is on. |
| `esx_multicharacter:relog` | Net event | Starts a relog. |
| `esx_multicharacter:chooseCharacter` | `xLib` callback | Same as `CharacterChosen`, returns `{ success, error? }`. |
| `esx_multicharacter:deleteCharacter` | `xLib` callback | Returns `{ success, error? }`. |
| `esx_multicharacter:relog` | `xLib` callback | Returns `{ success, error? }`. Used by `/relog`. |

The resource listens to `esx_identity:completedRegistration` to create the new character and then fires `esx:onPlayerJoined` with the `charN` prefix. For relog it triggers `esx:playerLogout`, which saves and unloads the xPlayer.

Possible `error` values: `delete_disabled`, `invalid_character`, `busy`, `rate_limited`, `stale_session`, `database_error`, `refresh_failed`, `already_loaded`, `disabled`, `not_loaded`.

### Client [#client]

| Name | Description |
| --- | --- |
| `esx_multicharacter:SetupUI` | Secure net event from the server with `(characters, slots)`. Opens the selection UI. |
| `esx:onPlayerLogout` | Fades out and reopens the selection after a relog. |

### Reacting to a character switch [#reacting-to-a-character-switch]

Use the ESX events instead of multicharacter internals. They fire for both first spawn and relog:

```lua title="client/main.lua"
RegisterNetEvent('esx:playerLoaded', function(xPlayer, isNew, skin)
    ESX.PlayerData = xPlayer
    -- build blips, jobs, UI for the selected character
end)

RegisterNetEvent('esx:onPlayerLogout', function()
    -- clean up anything tied to the previous character
end)
```

## Common issues [#common-issues]

- **"ESX Requires Onesync Infinity to work".** Add `set onesync on` to `server.cfg`.
- **"Cannot Find the servers mysql_connection_string".** The resource parses the database name from `mysql_connection_string`. Use the `mysql://user:pass@host/database` or `database=...;` form.
- **"identifier-active" when joining.** Another connected player (or a stale session) uses the same license. The resource cleans stale entries automatically if the old player no longer has a ping. With `EnableDebug` on in es_extended, the duplicate check is skipped for testing.
- **Black screen after selecting a character.** Usually a failed schema migration or a resource that errors in `esx:playerLoaded`. Check the server console.
- **Players see the default slot count after `/setslots`.** Make sure you passed the license without the `char1:` prefix.

## Related [#related]

- [esx_identity](/docs/core/esx_identity)
- [esx_skin](/docs/core/esx_skin)
- [es_extended configuration](/docs/es_extended/config)
- [Client events](/docs/es_extended/events/client)
- [Database schema](/docs/guides/database-schema)
