# esx_identity (/docs/core/esx_identity)

`esx_identity` is the ESX Legacy resource that asks a new player for their character's first name, last name, date of birth, sex and height, validates the input on the server and stores it in the `users` table. Use it on every ESX server; both the single-character flow and [esx_multicharacter](/docs/core/esx_multicharacter) depend on it.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_identity` |
| Version | 1.16.3 (fxmanifest, ships with ESX Legacy 1.16.4) |
| Side | Client + server, NUI form built with Vue |
| Dependencies | `es_extended`, `esx_lib`, `oxmysql`, [esx_skin](/docs/core/esx_skin) (opened after registration) |
| Database | Adds `firstname`, `lastname`, `dateofbirth`, `sex`, `height` to `users` |

## Installation [#installation]

Run the bundled SQL once if your `users` table does not have the identity columns yet (the official txAdmin recipe already includes them):

```sql title="esx_identity.sql"
ALTER TABLE `users`
	ADD COLUMN `firstname` VARCHAR(16) NULL DEFAULT NULL,
	ADD COLUMN `lastname` VARCHAR(16) NULL DEFAULT NULL,
	ADD COLUMN `dateofbirth` VARCHAR(10) NULL DEFAULT NULL,
	ADD COLUMN `sex` VARCHAR(1) NULL DEFAULT NULL,
	ADD COLUMN `height` INT NULL DEFAULT NULL;
```

Then ensure it after `es_extended` and `esx_lib`:

```bash title="server.cfg"
ensure esx_lib
ensure es_extended
ensure esx_identity
```

## How it works [#how-it-works]

<Steps>
<Step>
### Player connects [#player-connects]

Without multicharacter, the server reads the identity columns during `playerConnecting`. With multicharacter, [esx_multicharacter](/docs/core/esx_multicharacter) triggers the form when the player picks an empty slot.
</Step>
<Step>
### Form or restore [#form-or-restore]

If `firstname` is empty, the client receives `esx_identity:showRegisterIdentity` and opens the form. Otherwise the stored identity is applied to the xPlayer and the client gets `esx_identity:alreadyRegistered`.
</Step>
<Step>
### Server-side validation [#server-side-validation]

The `esx_identity:registerIdentity` callback re-checks every field (see [Validation rules](#validation-rules)), formats the names (`jOHN` becomes `John`), saves the row and pushes the data to the client.
</Step>
<Step>
### Skin creation [#skin-creation]

Without multicharacter, the client fires `esx_skin:playerRegistered`, which opens the [esx_skin](/docs/core/esx_skin) creator for players without a saved skin.
</Step>
</Steps>

## Configuration [#configuration]

All keys live in `config.lua`.

| Key | Default | Description |
| --- | --- | --- |
| `Config.Locale` | `ESX.GetConfig().Locale` | Language of notifications and the form. |
| `Config.EnableCommands` | `ESX.GetConfig().EnableDebug` | Registers `/char` and `/chardel`. |
| `Config.DateFormat` | `"DD/MM/YYYY"` | Display and storage format. Choices: `DD/MM/YYYY`, `MM/DD/YYYY`, `YYYY/MM/DD`. See [Common issues](#common-issues). |
| `Config.MaxNameLength` | `20` | Names must be shorter than this (byte length). |
| `Config.MinHeight` | `120` | Minimum height in centimeters. |
| `Config.MaxHeight` | `220` | Maximum height in centimeters. |
| `Config.MaxAge` | `100` | Oldest allowed age. The youngest allowed age is fixed at 18. |
| `Config.FullCharDelete` | `true` | `/chardel` also resets `bank_savings` and `caution` addon accounts and the `user_ears`, `user_glasses`, `user_helmet`, `user_mask` datastores. |
| `Config.EnableDebugging` | `ESX.GetConfig().EnableDebug` | Registers the `xPlayerGet*` debug commands. |

## Validation rules [#validation-rules]

The server never trusts the NUI. A registration is rejected with an error notification when:

| Field | Rule |
| --- | --- |
| `firstname`, `lastname` | String, valid locale characters, length between 1 and `MaxNameLength - 1`. |
| `sex` | `m`, `M`, `f` or `F` (stored lowercase). |
| `dateofbirth` | Matches `DD/MM/YYYY`, is a real calendar date, year between `currentYear - MaxAge` and `currentYear - 18`. |
| `height` | Number between `MinHeight` and `MaxHeight`. |

A player that is already registered cannot register again (`already_registered`).

## Reading identity data [#reading-identity-data]

After loading, the identity is stored on the xPlayer and replicated to the player's state bag.

<Tabs items={['Server', 'Client']}>
<Tab value="Server">

```lua title="server/main.lua"
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end

local fullName  = xPlayer.getName()            -- "John Doe"
local firstName = xPlayer.get('firstName')
local lastName  = xPlayer.get('lastName')
local dob       = xPlayer.get('dateofbirth')   -- "24/12/1995"
local sex       = xPlayer.get('sex')           -- "m" or "f"
local height    = xPlayer.get('height')        -- 180

-- also available to any resource via state bags
local state = Player(source).state
print(state.firstName, state.lastName)
```

</Tab>
<Tab value="Client">

```lua title="client/main.lua"
local data = ESX.GetPlayerData()
print(data.firstName, data.lastName, data.dateofbirth, data.sex, data.height)

-- or the replicated state bag
print(LocalPlayer.state.name)
```

</Tab>
</Tabs>

## Events [#events]

### esx_identity:completedRegistration [#esx_identitycompletedregistration]

Server event fired by esx_identity when a player finishes the form **in multicharacter mode** only. esx_multicharacter listens to it to create the new character. In single-character mode the identity is saved directly and this event is not fired.

```lua
AddEventHandler('esx_identity:completedRegistration', function(source, data) end)
```

| Name | Type | Description |
| --- | --- | --- |
| `source` | `number` | Player server ID. |
| `data.firstname` | `string` | Formatted first name. |
| `data.lastname` | `string` | Formatted last name. |
| `data.dateofbirth` | `string` | Date in `Config.DateFormat`. |
| `data.sex` | `string` | `"m"` or `"f"`. |
| `data.height` | `number` | Height in centimeters. |

```lua title="server/main.lua"
AddEventHandler('esx_identity:completedRegistration', function(source, data)
    print(('Player %s registered as %s %s'):format(source, data.firstname, data.lastname))
end)
```

### esx_identity:showRegisterIdentity [#esx_identityshowregisteridentity]

Client event that opens the registration form. It waits for the NUI and the loading screen, then opens the form unless the player is dead. It also triggers `esx_skin:resetFirstSpawn`. It is registered as a net event, so the server can send it:

```lua title="server/main.lua"
TriggerClientEvent('esx_identity:showRegisterIdentity', playerId)
```

<Callout type="warn">
Opening the form for a player that is already registered does nothing useful: the server rejects the submission with "You have already registered." Clear the identity first (for example with `/chardel`).
</Callout>

### Internal events [#internal-events]

| Event | Side | Description |
| --- | --- | --- |
| `esx_identity:setPlayerData` | Client (secure net event) | Server pushes `firstName`, `lastName`, `dateOfBirth`, `sex`, `height` into `ESX.PlayerData`. |
| `esx_identity:alreadyRegistered` | Client (secure net event) | Sent to registered players; fires `esx_skin:playerRegistered` once the loading screen is gone. |
| `esx_identity:registerIdentity` | Server callback | Receives the form payload. Do not call it from other resources. |

## Commands [#commands]

| Command | Permission | Enabled by | Description |
| --- | --- | --- | --- |
| `/char` | `user` | `Config.EnableCommands` | Shows your active character name. |
| `/chardel` | `admin` | `Config.EnableCommands` | Wipes your identity and skin and reopens the form. |
| `/xPlayerGetFirstName` | `user` | `Config.EnableDebugging` | Prints `xPlayer.get('firstName')`. |
| `/xPlayerGetLastName` | `user` | `Config.EnableDebugging` | Prints `xPlayer.get('lastName')`. |
| `/xPlayerGetFullName` | `user` | `Config.EnableDebugging` | Prints `xPlayer.getName()`. |
| `/xPlayerGetSex` | `user` | `Config.EnableDebugging` | Prints `xPlayer.get('sex')`. |
| `/xPlayerGetDOB` | `user` | `Config.EnableDebugging` | Prints `xPlayer.get('dateofbirth')`. |
| `/xPlayerGetHeight` | `user` | `Config.EnableDebugging` | Prints `xPlayer.get('height')`. |

## Common issues [#common-issues]

- **Registration always fails with "Invalid Format (DOB)".** The form sends the date in `Config.DateFormat`, but the server validator only parses `DD/MM/YYYY`. With `YYYY/MM/DD` every submission fails, and with `MM/DD/YYYY` dates with a day above 12 fail. Keep the default format.
- **Long names get cut or the query errors.** `MaxNameLength` is 20, but the SQL columns are `VARCHAR(16)`. Either lower `Config.MaxNameLength` to `17` or widen the columns.
- **Form never opens.** The client waits for both the NUI `ready` message and `esx:loadingScreenOff`. A custom loading screen that never shuts down blocks the form. Check the F8 console for "Waiting for esx_identity NUI..".
- **"identifier-missing" on connect.** The player has no license identifier (for example `sv_lan`). Fix the identifier setup in `es_extended`.
- **Height notification says inches.** The English debug string reads "Inches", but the value is stored in centimeters.

## Related [#related]

- [esx_multicharacter](/docs/core/esx_multicharacter)
- [esx_skin](/docs/core/esx_skin)
- [xPlayer functions](/docs/es_extended/server/xplayer)
- [Player data](/docs/es_extended/playerdata)
- [Database schema](/docs/guides/database-schema)
