Engineering
Designing a database for a FiveM server
Database design for FiveM scripts: how ESX and QBCore structure players and vehicles, primary keys and identifiers, indexes, JSON columns versus separate tables, utf8mb4, timestamps, and patterns that keep a growing server fast.
Overview
A roleplay server’s database starts small — a players table and a vehicles table — and grows into dozens of tables written by dozens of scripts. Decisions made on day one about keys, indexes and where to store JSON decide whether queries stay instant at ten thousand characters or crawl. This guide covers the patterns that hold up.
How the frameworks do it
| Data | ESX | QBCore |
|---|---|---|
| Characters | users (key: identifier) | players (key: citizenid) |
| Vehicles | owned_vehicles (by owner, plate) | player_vehicles (by citizenid, plate) |
| Licences | user_licenses | Player metadata (JSON) |
| Jobs | jobs, job_grades tables | qb-core/shared/jobs.lua (code) |
When writing a script for one framework, reference its identifier rather than inventing your own. A table keyed by the same citizenid joins cleanly with the framework’s own data.
A well-designed table
CREATE TABLE IF NOT EXISTS `fines` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`citizenid` VARCHAR(50) NOT NULL,
`amount` INT UNSIGNED NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`issued_by` VARCHAR(50) NOT NULL,
`paid` TINYINT(1) NOT NULL DEFAULT 0,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_fines_citizen` (`citizenid`),
KEY `idx_fines_unpaid` (`citizenid`, `paid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;- Auto-increment primary key for the row itself.
- The character identifier as a normal, indexed column.
- Money as integers (cents or whole dollars), never floats.
- A composite index for the query you run most (“unpaid fines for this character”).
- InnoDB for transactions and row locking.
Indexes: the difference between 1 ms and 1 second
Without an index, SELECT … WHERE owner = ? reads every row in the table. With a few hundred rows nobody notices; with a hundred thousand vehicles it is a slow query every time a garage opens. Add an index to every column in a WHERE, JOIN or ORDER BY you run often.
ALTER TABLE `player_vehicles` ADD INDEX `idx_vehicles_citizen` (`citizenid`);oxmysql’s slow-query warning tells you which queries need one — see the oxmysql guide.
JSON columns vs separate tables
| Store as JSON when… | Use columns or a table when… |
|---|---|
| The data is read and written as a whole (a character’s clothing) | You search or filter by it |
| Its shape varies (item metadata) | You need to count or sum it |
| It is small | It grows without limit (logs, transactions) |
Habits that keep it healthy
- Create tables with
IF NOT EXISTSin an install SQL file shipped with the resource. - Never store passwords, tokens or webhook URLs in tables players can reach through scripts.
- Clean up: delete old logs on a schedule instead of keeping everything forever.
- Back up nightly and test restoring — see the minimap studio’s server.cfg guide for where the database fits in the server.
- Use transactions for anything that moves money between rows.
Frequently asked questions
What database does FiveM use?
FiveM itself has no database; frameworks and scripts use MySQL or MariaDB, usually through oxmysql.
Should I store data as JSON in FiveM?
For small, whole-object data such as appearance, yes. For anything you search, count or that keeps growing, use proper columns or a separate table.
Why is my garage query slow?
The column you filter by (owner or citizenid) is probably not indexed. Add an index.
What charset should FiveM tables use?
utf8mb4, so names and messages with emoji or special characters save correctly.
Need this built, not just explained?
Ten years of FiveM work, from Lua to NUI
Custom resources, React NUI, ESX / QBCore / Qbox integration, OneSync performance audits and security reviews — plus the websites and SEO around your server brand.
Related guides
- EngineeringUsing oxmysql in FiveM: setup, queries and good habitsPoint oxmysql at your database with set mysql_connection_string "mysql://user:password@localhost:3306/database" in server.cfg, add server_script '@oxmysql/lib/MySQL.lua' to your resource, and call MySQL.query.await, MySQL.single.await, MySQL.scalar.await, MySQL.insert.await, MySQL.update.await or MySQL.prepare.await with ? placeholders. Enable mysql_slow_query_warning to catch slow queries.
- FrameworksESX vs QBCore vs QboxData models, inventories, script ecosystems, performance and hiring cost — the real differences between the three FiveM frameworks.
- EngineeringFiveM server optimizationRead resmon properly, kill per-frame loops, tune OneSync and entity limits, and find the database query that is freezing your server.