Database Schema
Every ESX Legacy database table explained with columns, types, keys and the resources that use them, from users and jobs to billing, banking and owned_vehicles.
ESX Legacy stores all persistent data in a MySQL or MariaDB database accessed through oxmysql. The complete schema lives in [SQL]/legacy.sql in esx_core, and each core resource and addon ships its own .sql file with the tables it needs. This page lists every table, its columns and which resource reads or writes it.
How the schema is created
There are three ways tables end up in your database:
legacy.sql: the full schema with seed data (jobs, items, vehicles, fines). txAdmin's ESX recipe imports it for you.- Per-resource
.sqlfiles: for exampleesx_billing.sqloresx_identity.sql. Useful if you install resources one by one. - Automatic migrations: since 1.16.0,
es_extendedloads the SQL catalog fromesx_libon startup. It creates missing tables, adds missing columns and inserts missing seed rows fores_extendedand the official resources (identity, multicharacter, skin, addonaccount, billing, banking, garage, jobs and more). It records what it did inesx_catalog_imports. Several addons (esx_billing,esx_banking,esx_garage,esx_addonaccount,esx_license,esx_whitelist,esx_property) also run their own migrations.
Seed rows are only inserted when missing
The catalog inserts a seed row only if no row with the same key exists (jobs.name, job_grades (job_name, grade), items.name and so on). It does not overwrite your own changes to labels or salaries.
Core tables (es_extended)
users
One row per character. Created by es_extended; several resources add columns.
| Column | Type | Default | Added by | Notes |
|---|---|---|---|---|
identifier | varchar(60) | none | es_extended | Primary key. License without prefix, or char<N>:<license> with multicharacter |
ssn | varchar(11) | none | es_extended | Unique (unique_ssn). Generated per character, read with xPlayer.getSSN() |
accounts | longtext | NULL | es_extended | JSON object of account name to balance |
group | varchar(50) | 'user' | es_extended | Permission group, see Admin permissions |
inventory | longtext | NULL | es_extended | JSON of item name to count (default inventory) |
job | varchar(20) | 'unemployed' | es_extended | Must match jobs.name |
job_grade | int(11) | 0 | es_extended | Must match job_grades.grade |
loadout | longtext | NULL | es_extended | JSON weapons list (default inventory) |
metadata | longtext | NULL | es_extended | JSON, read with xPlayer.getMeta() |
position | longtext | NULL | es_extended | JSON coords and heading |
firstname, lastname | varchar(16) | NULL | esx_identity | |
dateofbirth | varchar(10) | NULL | esx_identity | |
sex | varchar(1) | NULL | esx_identity | m or f |
height | int(11) | NULL | esx_identity | |
skin | longtext | NULL | esx_skin | JSON skinchanger data |
status | longtext | NULL | esx_status | Hunger, thirst and other statuses |
is_dead | tinyint(1) | 0 | esx_ambulancejob / esx_death | Used when Config.SaveDeathStatus is on |
death_time | bigint | NULL | esx_death | Added by esx_death.sql |
id | int(11) | auto increment | legacy.sql | Unique key id |
disabled | tinyint(1) | 0 | esx_multicharacter | Disabled character slots |
last_property | longtext | NULL | esx_property | Last property the player was in |
created_at | timestamp | CURRENT_TIMESTAMP | legacy.sql | |
last_seen | timestamp | NULL, ON UPDATE CURRENT_TIMESTAMP | legacy.sql | Updated whenever the row changes |
phone_number | varchar(20) | NULL | legacy.sql | Not read by es_extended; available for phone resources |
pincode | int | NULL | esx_banking | ATM PIN |
Example accounts value with the default Config.Accounts:
{ "bank": 50000, "money": 0, "black_money": 0 }jobs
| Column | Type | Default | Notes |
|---|---|---|---|
name | varchar(50) | none | Primary key, used in users.job and xPlayer.setJob() |
label | varchar(50) | NULL | Display name |
type | varchar(50) | 'civ' | Job type, filter with ESX.GetJobs(type). Seeds use civ, leo, ems, mechanic |
whitelisted | tinyint(1) | 0 | Used by esx_joblisting to hide jobs from the job center |
job_grades
| Column | Type | Default | Notes |
|---|---|---|---|
id | int(11) | auto increment | Primary key |
job_name | varchar(50) | NULL | References jobs.name (no foreign key) |
grade | int(11) | none | Grade number, starting at 0 |
name | varchar(50) | none | Internal grade name. boss grants the society boss menu by default |
label | varchar(50) | none | Display name |
salary | int(11) | none | Paycheck amount per Config.PaycheckInterval |
skin_male, skin_female | longtext | none | JSON uniform, '{}' if unused. Written by the esx_society uniform manager |
A job without any grade is ignored by ESX.RefreshJobs() with a warning. See Create a job.
items
| Column | Type | Default | Notes |
|---|---|---|---|
name | varchar(50) | none | Primary key |
label | varchar(50) | none | Display name |
weight | int(11) | 1 | Counted against Config.MaxWeight |
rare | tinyint(4) | 0 | Stored and sent to the client; no core gameplay effect |
can_remove | tinyint(4) | 1 | 0 blocks dropping and giving the item |
Not used when ox_inventory runs; items then come from ox_inventory/data/items.lua. See Add items.
owned_vehicles
Created by esx_vehicleshop.sql and legacy.sql; extended by esx_garage. Used by es_extended (extended vehicles), esx_garage, esx_vehicleshop, esx_policejob, esx_lscustom, esx_boat, esx_property and others.
| Column | Type | Default | Added by | Notes |
|---|---|---|---|---|
owner | varchar(60) | NULL | core | users.identifier. Indexed |
plate | varchar(12) | none | core | Primary key |
vehicle | longtext | NULL | core | JSON vehicle properties |
type | varchar(20) | 'car' | core | car, boat, aircraft... |
job | varchar(20) | NULL | core | Set for job vehicles |
stored | tinyint(4) | 0 | core | 1 = in a garage |
parking | varchar(60) | NULL | esx_garage | Garage the vehicle is parked in |
pound | varchar(60) | NULL | esx_garage | Impound location |
custom_name | varchar(50) | NULL | esx_garage | Player-chosen name |
is_favorite | tinyint(1) | 0 | esx_garage | |
last_used | int | NULL | esx_garage | |
mileage | decimal(10,2) | 0.00 | esx_garage |
esx_garage also creates composite indexes such as idx_owned_vehicles_owner_plate and idx_owned_vehicles_owner_stored_pound_plate, and tracks its schema version in esx_garage_migrations.
esx_catalog_imports
Created by the esx_lib SQL catalog.
| Column | Type | Notes |
|---|---|---|
resource | varchar(64) | Primary key, resource whose seed data was prepared |
version | varchar(32) | Catalog version, for example 1.16.0 |
locale | varchar(32) | Locale used for seeded labels |
updated_at | timestamp |
Character and identity tables
multicharacter_slots
Used by esx_multicharacter to give individual players more character slots.
| Column | Type | Notes |
|---|---|---|
identifier | varchar(60) | Primary key, license without the char prefix |
slots | int(11) | Number of slots. Indexed |
whitelist
| Column | Type | Notes |
|---|---|---|
identifier | varchar(60) | Primary key |
This table is still in legacy.sql, but the current esx_whitelist resource uses the two tables below instead.
esx_whitelist_config and esx_whitelist_identifiers
Created by esx_whitelist (whitelist.sql or automatically on start).
| Table | Column | Type | Notes |
|---|---|---|---|
esx_whitelist_config | setting_key | varchar(64) | Primary key |
setting_value | longtext | ||
updated_at | timestamp | Auto-updated | |
esx_whitelist_identifiers | identifier | varchar(128) | Primary key with type |
type | varchar(32) | Default 'whitelist', indexed | |
created_at | timestamp |
Society and shared data tables
addon_account and addon_account_data
Used by esx_addonaccount, esx_society (society money) and esx_vehicleshop. Society accounts are named society_<job>.
| Table | Column | Type | Notes |
|---|---|---|---|
addon_account | name | varchar(60) | Primary key, e.g. society_police |
label | varchar(100) | ||
shared | int(11) | 1 = one account for everyone (society), 0 = one per player | |
addon_account_data | id | int(11) | Primary key, auto increment |
account_name | varchar(100) | Indexed | |
money | int(11) | Balance | |
owner | varchar(60) | NULL for shared accounts, identifier otherwise. Indexed |
Unique key (account_name, owner). The 2026-10-02 hardening adds a generated owner_key column (COALESCE(owner, '')) with a unique key, so shared accounts cannot be duplicated either.
addon_inventory and addon_inventory_items
Used by esx_addoninventory for society and property stashes (default inventory).
| Table | Column | Type | Notes |
|---|---|---|---|
addon_inventory | name | varchar(60) | Primary key |
label | varchar(100) | ||
shared | int(11) | Shared or per player | |
addon_inventory_items | id | int(11) | Primary key, auto increment |
inventory_name | varchar(100) | Indexed | |
name | varchar(100) | Item name | |
count | int(11) | ||
owner | varchar(60) | NULL for shared. Indexed |
datastore and datastore_data
Generic JSON storage, used by esx_datastore, esx_accessories (user_mask, user_helmet...), esx_property and society resources.
| Table | Column | Type | Notes |
|---|---|---|---|
datastore | name | varchar(60) | Primary key |
label | varchar(100) | ||
shared | int(11) | ||
datastore_data | id | int(11) | Primary key, auto increment |
name | varchar(60) | Datastore name | |
owner | varchar(60) | Indexed | |
data | longtext | JSON |
Unique key (name, owner).
society_moneywash
Used by esx_society for money washing.
| Column | Type | Notes |
|---|---|---|
id | int(11) | Primary key, auto increment |
identifier | varchar(60) | Indexed |
society | varchar(60) | |
amount | int(11) |
Current esx_society also creates society_moneywash_operations (operation_id primary key, identifier, society, amount, state, created_at) on start and moves pending rows from society_moneywash into it.
Economy tables
billing
Used by esx_billing (and jobs that send bills).
| Column | Type | Notes |
|---|---|---|
id | int(11) | Primary key, auto increment |
identifier | varchar(60) | Player who must pay. Indexed |
sender | varchar(60) | Identifier of the sender |
target_type | varchar(50) | player or society |
target | varchar(60) | Receiving identifier or society account |
label | varchar(255) | |
amount | int(11) |
billing_payments
Created by esx_billing on start (and by the hardening script). It is a payment journal that makes paying a bill idempotent.
| Column | Type | Notes |
|---|---|---|
bill_id | int | Primary key |
operation_id | varchar(64) | Unique |
identifier, sender | varchar(60) | |
target_type | varchar(10) | |
target | varchar(100) | |
label | varchar(255) | |
amount | int | |
payment_account | varchar(20) | Account used to pay |
state | enum('claimed','applied') | Default claimed |
created_at, applied_at | timestamp |
Rows stuck in claimed should be inspected manually. The hardening script explicitly says not to replay or refund them automatically.
banking
Transaction history for esx_banking.
| Column | Type | Notes |
|---|---|---|
ID | int(11) | Primary key, auto increment |
identifier | varchar(46) in legacy.sql | esx_banking widens it to varchar(60) on start |
type | varchar(50) | Deposit, withdraw, transfer... |
amount | int(64) | |
time | bigint(20) | Unix time in ms |
balance | int(11) | Balance after the transaction |
label | varchar(255) |
Keys: idx_banking_identifier_time (identifier, time) and idx_banking_time (time).
fine_types
Fine catalog for esx_policejob.
| Column | Type | Notes |
|---|---|---|
id | int | Primary key, auto increment |
label | varchar(255) | |
amount | int | |
category | int | Groups fines in the police fine menu. Seed data uses 0 (traffic) to 3 (most severe) |
License tables
Used by esx_license, and through it by esx_dmvschool, esx_weaponshop, esx_boat and esx_drugs.
| Table | Column | Type | Notes |
|---|---|---|---|
licenses | type | varchar(60) | Primary key, e.g. dmv, drive, weapon |
label | varchar(60) | ||
user_licenses | id | int(11) | Primary key, auto increment |
type | varchar(60) | License type | |
owner | varchar(60) | Identifier |
Unique key uq_user_licenses_owner_type (owner, type) so a player cannot hold the same license twice.
Vehicle shop tables
All used by esx_vehicleshop (vehicles is also read by esx_lscustom).
| Table | Columns | Notes |
|---|---|---|
vehicles | name varchar(60), model varchar(60) (PK), price int(11), category varchar(60) | Vehicles for sale |
vehicle_categories | name varchar(60) (PK), label varchar(60) | Shop categories |
cardealer_vehicles | id int(11) (PK), vehicle varchar(255), price int(11) | Car dealer job stock |
vehicle_sold | id int(11) (PK), client, model, plate, soldby, date (all varchar(50)) | Sales log of the car dealer job |
rented_vehicles | vehicle varchar(60), plate varchar(12) (PK), player_name varchar(255), base_price int, rent_price int, owner varchar(60) | Rentals |
esx_vehicleshop also creates vehicle_rental_payments (plate, billing_date as primary key, owner, amount, operation_id unique, state) on start.
Other tables created by addons
| Table | Created by | Purpose |
|---|---|---|
esx_garage_migrations | esx_garage | Applied garage schema migrations (name, version, applied_at) |
esx_properties | esx_property | Property definitions as JSON (id, data) |
esx_property_migrations | esx_property | Applied property migrations |
bans | esx_adminmenu | Bans with identifier, reason, banned_by, expires_at |
admin_logs | esx_adminmenu | Admin action log (actor, target, action, payload) |
Inspecting your own database
-- Which catalog imports ran
SELECT * FROM esx_catalog_imports;
-- Jobs that have no grades (ignored by ESX)
SELECT j.name FROM jobs j
LEFT JOIN job_grades g ON g.job_name = j.name
WHERE g.id IS NULL;
-- Players whose job no longer exists (reset to unemployed on load)
SELECT identifier, job FROM users
WHERE job NOT IN (SELECT name FROM jobs);Common issues
Unknown column 'ssn'on old databases: startes_extended1.16 once, its migration adds and fillsusers.ssn. It stops if duplicate SSNs exist.- "Economy tables must use InnoDB": convert
billing,usersandaddon_account_datawithALTER TABLE ... ENGINE=InnoDBafter a backup. - Duplicate key errors in the hardening script: you have duplicate
user_licensesoraddon_account_datarows. Reconcile them by hand; the script never deletes financial rows. - Editing JSON columns while the server runs: online players are kept in memory and overwrite your change on the next save. Edit offline players only, or use in-game commands.
Related
Frequently asked questions
Independent documentation
Last updated on
ESX vs QBCore
A neutral comparison of ESX Legacy and QBCore for FiveM servers covering architecture, data storage, API style, ecosystem, learning curve and migration.
Performance
Optimize an ESX Legacy server with resmon and the profiler, smarter thread waits, state bags instead of events, database indexes, paycheck batching and OneSync.