oxmysql for FiveM
The MySQL wrapper the modern FiveM stack runs on: promise-based, prepared-statement aware and a drop-in replacement for mysql-async.
Every persistent thing on a FiveM server — characters, vehicles, inventories, bans, and the settings behind a hosted loading screen — ends up in MySQL. oxmysql is the current standard client: it uses real prepared statements, returns promises, and ships a compatibility layer so mysql-async and ghmattimysql scripts keep working while you migrate.
At a glance
| Maintainer | Overextended |
| Latest release | v2.14.1 · 2026-05-04 |
| Last code change | 2026-07-03 |
| Licence | LGPL-3.0 |
| Written in | TypeScript |
| GitHub stars | 360 |
| Status | Active |
Figures read from the repository on 2026-09-22.
Why servers run it
- Prepared statements by default, which closes the SQL injection hole the older wrappers left wide open.
- Promise-based with await support, so database code reads top to bottom.
- Drop-in shims for mysql-async and ghmattimysql, so migration is incremental rather than a big bang.
- A built-in slow-query log, which is usually how you find the script quietly costing you 40ms a tick.
Installing oxmysql
1. Start it first
oxmysql has to be the first resource in server.cfg that touches the database.
2. Set the connection string
set mysql_connection_string in server.cfg, in either URI or key=value form. Do not put it in a config.lua that ends up in a public repository.
3. Turn on the slow-query log
set mysql_slow_query_warning 150 prints anything over 150ms with the query attached.
4. Use the shim while migrating
Old scripts keep working through the compatibility layer; convert them as you touch them rather than all at once.
Working examples
server.cfg — the connection string
set mysql_connection_string "mysql://user:password@localhost:3306/fivem?charset=utf8mb4"
set mysql_slow_query_warning 150
set mysql_debug falseReading and writing, the modern way
-- Single row
local row = MySQL.single.await(
'SELECT citizenid, charinfo FROM players WHERE license = ?', { license })
-- Many rows
local screens = MySQL.query.await(
'SELECT slug, title FROM loading_screens WHERE owner = ?', { userId })
-- Insert, returning the new id
local id = MySQL.insert.await(
'INSERT INTO loading_screens (owner, slug, title) VALUES (?, ?, ?)',
{ userId, slug, title })A transaction that either all lands or none does
MySQL.transaction.await({
{ query = 'UPDATE accounts SET balance = balance - ? WHERE id = ?', values = { 500, from } },
{ query = 'UPDATE accounts SET balance = balance + ? WHERE id = ?', values = { 500, to } },
})What goes wrong
Never build a query by concatenating player input. Use ? placeholders. The whole reason oxmysql exists is that the previous generation made this easy to get wrong.
`.await` blocks the coroutine it runs in. Inside a per-frame loop that is a stall; inside an event handler it is fine.
The connection string lives in server.cfg, which means server.cfg contains a database password. Keep it out of version control.
Two MySQL resources at once — oxmysql and mysql-async both started — gives you two connection pools and a confusing set of errors.
oxmysql — questions people ask
Is oxmysql faster than mysql-async?
Can I migrate gradually?
Where are the oxmysql docs?
Runs alongside
The original FiveM MySQL wrapper: still installed on a very large number of live servers, no longer the right choice for new code.
The shared library half the modern FiveM ecosystem depends on: menus, notifications, progress bars, zones, callbacks and a cache.
The web panel bundled with every FXServer build: process management, recipes, players, bans, scheduled restarts and live console.