Engineering
KVP storage in FiveM: saving small data without a database
How FiveM resource KVP (key-value pairs) works: SetResourceKvp, GetResourceKvpString/Int/Float, deleting and iterating keys, client vs server storage, and when KVP beats a database — HUD positions, settings and flags.
Overview
Not everything belongs in MySQL. A player’s HUD layout, their preferred chat colour or whether they have seen the tutorial are tiny, personal values — and FiveM has a built-in key-value store for exactly that. It needs no database, no queries and no connection string.
The API
local function saveHud(settings)
SetResourceKvp('hud_settings', json.encode(settings))
end
local function loadHud()
local raw = GetResourceKvpString('hud_settings')
return raw and json.decode(raw) or { scale = 1.0, showSpeed = true }
end
SetResourceKvpInt('tutorial_done', 1)
print(GetResourceKvpInt('tutorial_done')) -- 1
DeleteResourceKvp('hud_settings')| Function | Does |
|---|---|
SetResourceKvp(key, string) | Store a string |
SetResourceKvpInt(key, int) / SetResourceKvpFloat | Store a number |
GetResourceKvpString/Int/Float(key) | Read it (nil/0 if missing) |
DeleteResourceKvp(key) | Remove it |
StartFindKvp(prefix) … FindKvp … EndFindKvp | List keys starting with a prefix |
Listing keys
local handle = StartFindKvp('outfit_')
while true do
local key = FindKvp(handle)
if not key then break end
print(key, GetResourceKvpString(key))
end
EndFindKvp(handle)Client vs server KVP
| Client KVP | Server KVP | |
|---|---|---|
| Stored | On the player’s PC, for your server | With the server’s data |
| Survives | Across sessions on that PC | Across server restarts |
| Visible to | That player (and editable by them) | Only the server |
| Good for | UI settings, keybind hints, seen-tutorial flags | Small server state, counters, simple flags |
KVP or database?
| Use KVP for… | Use the database for… |
|---|---|
| Per-player UI preferences | Anything other players or staff must see |
| Small flags and counters | Money, items, vehicles, characters |
| Data only one resource needs | Data you query, join or report on |
When the database is the right answer, see the oxmysql guide.
Frequently asked questions
What is KVP in FiveM?
A built-in key-value store scoped to each resource, available on the client and the server, for small values like settings and flags.
Where is client KVP stored?
On the player’s own PC, per server and per resource.
Can I store a table in KVP?
Encode it first: SetResourceKvp(key, json.encode(t)), then json.decode when reading.
Should I store money in KVP?
No. Client KVP can be edited by the player; valuable data belongs in the server’s database.
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
- EngineeringConvars in FiveM: configuration that lives in server.cfgset name value creates a server-only convar. sets name value also publishes it to the server list (for tags, locale, banners). setr name value replicates it to clients so client scripts can read it. Read convars with GetConvar(name, default) (strings) or GetConvarInt(name, default) (integers). Never use setr or sets for secrets.
- 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.
- EngineeringDesigning a database for a FiveM serverGive every table a primary key, reference characters by one stable identifier (citizenid on QBCore, identifier on ESX), index every column you search or join on, use utf8mb4 so names with emoji work, store small flexible data as JSON but put anything you search or count in real columns, and add created_at/updated_at timestamps.
- Getting startedLua tables for FiveM developersA table can be an array ({ 'a', 'b' }, indexed from 1) or a dictionary ({ price = 5 }), or both. Loop arrays with ipairs and dictionaries with pairs. #t counts only the array part up to the first gap. Tables are passed by reference, so copying requires a copy function. json.encode and json.decode convert tables for NUI, HTTP and databases.