Skip to main content
TRACK B·DATA AND PERSISTENCE·Verified June 2026 · Lua 5.4 · ox_lib 3.x
Learning with an AI assistant?
Copies this lesson plus 2026 ground rules (no lua54 'yes', Cfx.re Portal, correct callback signatures) as a ready-to-paste mentor prompt.

Database with oxmysql: CRUD without the footguns

Almost every server you build will save state. Bans, money, garages, jobs, all of it lives in a database. oxmysql is the modern async driver the FiveM community has standardised on, and it ships from Overextended, the same group behind ox_lib and ox_inventory. This lesson builds insert, read, and update as a working resource, then walks the code line by line so you understand the mental model instead of copying it.

You'll build
A working resource named qu_oxmysql_crud that inserts a row, reads it back, and updates it, all from one command.
Time
~25 minutes
You need
A running MariaDB database wired to oxmysql, your resources folder open, and txAdmin Live Console ready.
You'll learn
The four oxmysql helpers, what the question mark placeholders do, why await blocks the right way, and when to reach for a database at all.
BEFORE YOU START

Build it

Make the resource folder

The server has one folder for this lesson.

Inside your server's resources folder, create this folder:

code
resources/qu_oxmysql_crud

Create the files

Every file named in the manifest exists.

Create this exact file layout:

code
resources/qu_oxmysql_crud/
fxmanifest.lua
server.lua

Write fxmanifest.lua

FiveM knows which files to load and that oxmysql must load first.

Open fxmanifest.lua and paste this:

code
fx_version 'cerulean'
game 'gta5'

server_script '@oxmysql/lib/MySQL.lua'
server_script 'server.lua'

dependencies {
'oxmysql'
}

Then run this SQL against your database before starting the resource:

code
CREATE TABLE IF NOT EXISTS qu_oxmysql_crud_rows (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(80) NOT NULL,
amount INT NOT NULL DEFAULT 0
);

Write the lesson code

Insert, read, and update are now runnable code.

Open server.lua and paste this:

code
RegisterCommand('dbtest', function(src)
local id = MySQL.insert.await('INSERT INTO qu_oxmysql_crud_rows (name, amount) VALUES (?, ?)', { 'lesson', 100 })
local row = MySQL.single.await('SELECT * FROM qu_oxmysql_crud_rows WHERE id = ?', { id })
print('[qu_oxmysql_crud] selected ' .. row.name .. ' ' .. row.amount)
MySQL.update.await('UPDATE qu_oxmysql_crud_rows SET amount = ? WHERE id = ?', { 150, id })
print('[qu_oxmysql_crud] updated ' .. id)
end, true)

Start and test it

The expected proof appears in the server console.

Open server.cfg and add this line:

code
ensure qu_oxmysql_crud

Save, then run this in the server console (txAdmin Live Console):

code
restart qu_oxmysql_crud

Now run the test command in the same console:

code
dbtest

The number after updated is the new row id, so it climbs by one each time you run dbtest. The first run prints 1, the next prints 2, and so on. The selected lesson 100 line stays the same because every run inserts the same name and amount before reading it back.

See your write land in HeidiSQL

The exact row your code inserted is now visible in the database GUI.

The console proved the code ran. Now prove the data is really on disk. Open HeidiSQL, connect to the same database your mysql_connection_string points at, click qu_oxmysql_crud_rows in the left tree, and open the Data tab. You should see the row your INSERT created, already carrying the 150 your UPDATE set:

HeidiSQL · Data tab
idnameamount
1lesson150
qu_oxmysql_crud_rows · 1 row - the same row dbtest just inserted and updated

This is the whole point of a database: the console message disappears the moment you close the server, but this row stays. Run dbtest a second time and refresh the Data tab (F5) to watch a second row appear with the next id:

HeidiSQL · Data tab
idnameamount
1lesson150
2lesson150
qu_oxmysql_crud_rows · 2 rows - each dbtest run adds one row and bumps the id

New to this GUI? The full connect, create database, and run SQL walkthrough is in HeidiSQL hands on. If 127.0.0.1:3306 refuses the connection, your MariaDB is not running, not HeidiSQL being broken.

idnamemoneyjob
2Blair4200mechanic
1 of 3 rows match.

Keep reading the full lesson

Sign in to start, then unlock every step of this lesson and the full FiveM School with a membership.

Still ahead in this lesson
  • How it works
  • If something went wrong
  • What you can do now
  • Try it yourself

The remainder of CRUD and prepared queries with oxmysql is available to FiveM School members.