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.
ESX Legacy and QBCore are the two most widely used roleplay frameworks for FiveM. Both give you a persistent player object with money, a job and an inventory, both run on Lua and both use oxmysql for the database. The real differences are in how data is stored, how the API looks and which scripts already exist for each. This page compares them as neutrally as possible so you can choose, or understand what a migration involves.
Scope
This site documents ESX Legacy 1.16.4 and every ESX statement below is checked against its source code. For QBCore, read the official documentation at qbcore.net. QBCore changes over time, so verify QBCore specifics there before you make a decision.
Quick comparison
| Topic | ESX Legacy | QBCore |
|---|---|---|
| Core resource | es_extended plus esx_lib | qb-core |
| Getting the core object | exports['es_extended']:getSharedObject() or @es_extended/imports.lua | exports['qb-core']:GetCoreObject() |
| Player object | xPlayer with methods like xPlayer.addAccountMoney() | Player object with methods under Player.Functions |
| Jobs and grades | Database tables jobs and job_grades | Defined in shared Lua files by default |
| Items | Database table items (or ox_inventory item files) | Defined in shared Lua files by default |
| Money | Accounts from Config.Accounts: money, bank, black_money | Money types configured in QBCore (cash, bank and others) |
| Database driver | oxmysql (hard dependency in fxmanifest.lua) | oxmysql |
| OneSync | Required (OneSync Infinity) | See QBCore docs |
| License | GPL-3.0 (SPDX header in every ESX file) | See the QBCore repository |
Architecture
ESX Legacy
ESX Legacy is split into a core and optional resources:
es_extendedis the framework core. It loads players, manages accounts, jobs, groups, the default inventory, commands, callbacks and paychecks.esx_libis a shared library thates_extendeddepends on. It provides callbacks, table and math helpers, points, streaming helpers and the SQL catalog that creates and seeds tables on startup.- Core resources like
esx_multicharacter,esx_identity,esx_skin,esx_contextandesx_notifyhandle character creation and UI. - Addons like
esx_policejob,esx_society,esx_billingandesx_garageadd gameplay.
The design is database-first: jobs, grades and items live in SQL tables. You can add a job with a single INSERT and the /refreshjobs command, or at runtime with ESX.CreateJob(). See Create a job and Add items.
ESX refuses to start without OneSync Infinity: the join handler rejects players with "ESX Requires Onesync Infinity to work" when the onesync convar is off or legacy.
QBCore
QBCore is built around the qb-core resource and a family of qb-* resources. By default, static data like jobs, items, gangs and vehicles is defined in Lua files inside qb-core/shared, and player data is stored in a players table. Check qbcore.net for the current file layout, because it has changed between versions.
What this means in practice
| You want to... | ESX Legacy | QBCore |
|---|---|---|
| Add a job | INSERT into jobs and job_grades, then /refreshjobs | Edit the shared jobs file and restart |
| Add an item | INSERT into items, then /refreshitems (or edit ox_inventory items) | Edit the shared items file (or the inventory resource) |
| Change data without a restart | Supported for jobs and items through the refresh commands | Depends on the version and resource |
| Edit data from a web panel | Easy, it is all SQL | Requires editing Lua files |
Database-first is convenient for admin panels and live changes. File-first is convenient for version control and code review. Neither is wrong.
API style
Both frameworks expose the same concepts with different names. Here is the same task, giving a player money and reading their job, in ESX:
local ESX = exports['es_extended']:getSharedObject()
RegisterCommand('payday', function(source)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
xPlayer.addAccountMoney('bank', 500, 'Payday command')
local job = xPlayer.getJob()
xPlayer.showNotification(('Paid as %s (%s)'):format(job.label, job.grade_label))
end, false)In QBCore the equivalent uses the QBCore player object and its Functions table. If you are porting a script, map each call one by one:
| Concept | ESX Legacy (verified) | Notes |
|---|---|---|
| Get player | ESX.GetPlayerFromId(source) | Returns nil when the player is not loaded |
| Add money | xPlayer.addAccountMoney(account, amount, reason) | addMoney() is a shortcut for the money account |
| Remove money | xPlayer.removeAccountMoney(account, amount, reason) | Returns false if the balance would go negative |
| Set job | xPlayer.setJob(name, grade, onDuty) | Grade must exist in job_grades |
| Check item | xPlayer.getInventoryItem(name) / xPlayer.hasItem(name) | Proxied to ox_inventory when it is running |
| Server callback | ESX.RegisterServerCallback(name, function(source, cb, ...) end) | See Callbacks |
| Player loaded event | esx:playerLoaded | Server: (playerId, xPlayer, isNew) |
ESX also replicates some player data through state bags: Player(source).state.job, .group and .name are set by es_extended, which helps when you write framework-agnostic code. See Performance.
Ecosystem
ESX has been around since 2017, so a very large number of free and paid scripts target it, including many written for old ESX versions. Most of those still run on ESX Legacy because the classic API (ESX.GetPlayerFromId, xPlayer.addMoney, esx:playerLoaded) is kept. Old scripts usually need small fixes, covered in Convert old ESX scripts.
QBCore has its own large ecosystem of qb-* and third-party resources. Many commercial script authors ship both an ESX and a QBCore version, or a single script with a framework bridge.
The overextended resources (oxmysql, ox_lib, ox_inventory, ox_target) work with both frameworks. ESX Legacy detects ox_inventory automatically. See ox_inventory with ESX.
Do not mix frameworks
es_extended contains a resource guard that stops qb-core (and old resources like essentialmode and es_admin2) when it detects them and prints "WE STOPPED A RESOURCE THAT WILL BREAK ESX". Pick one framework per server.
Performance
You will find many claims online that one framework is "faster". We do not repeat any numbers here because we know of no neutral, reproducible benchmark. In practice:
- An idle framework core costs very little. What hurts servers is scripts with
Wait(0)loops, unindexed queries and chatty events. - Both frameworks can run large servers when the installed scripts are well written.
- Measure your own server with
resmonand the FiveM profiler instead of trusting comparisons.
ESX Legacy has some built-in measures, for example paychecks are paid in batches (Config.PaycheckBatchSize, Config.PaycheckBatchDelay, Config.PaycheckJitter) and players are saved every 10 minutes in one background thread. Details are in Performance.
Learning curve
- ESX has the most tutorials and forum answers of any FiveM framework, but many of them are outdated (
esx:getSharedObjectevents,mysql-async). Learning to recognize old patterns is part of learning ESX. The core API is small and consistent: oneESXobject, onexPlayerobject. - QBCore documentation and tutorials are mostly newer. If you are used to its
Functionsnaming, ESX will look different but conceptually similar.
If your team already knows one framework, that is usually the strongest argument for it.
Migrating between frameworks
There is no official converter in ESX Legacy for QBCore data. A migration is a project, not a setting. Plan it like this:
Inventory your scripts
List every resource and check whether an ESX version exists. Scripts that only exist for the other framework must be rewritten or replaced.
Map your data
Player money, jobs, inventories and vehicles are stored in different tables and formats. In ESX, player data is in users (accounts and inventory are JSON in accounts and inventory) and vehicles in owned_vehicles. See the Database schema.
Write and test a one-off migration
Write a script or SQL that converts the data, and run it on a copy of the database. Check identifiers: ESX stores the license without the license: prefix, and with esx_multicharacter as char1:<license>.
Run both servers side by side
Keep the old server running until the new one is tested with real players. Never run both frameworks in one server.
How to decide
- Choose ESX Legacy if you want database-driven jobs and items, need a specific ESX script, or your developers already know ESX.
- Choose QBCore if the scripts you need exist only for QBCore or your team already knows it. Start at qbcore.net.
- If you are undecided, list the 10 scripts your server cannot live without and check which framework they support.
Common issues
- "WE STOPPED A RESOURCE THAT WILL BREAK ESX": you have
qb-coreor another conflicting resource in yourserver.cfg. Remove it. - A QBCore script throws
attempt to index a nil value (global 'QBCore')on an ESX server: the script needs a QBCore core. Use its ESX version or a bridge. - Players kicked with "ESX Requires Onesync Infinity": set
onesync on(txAdmin does this for you) before starting ESX.
Related
Frequently asked questions
Independent documentation
Last updated on
Convert Old ESX Scripts
How to convert ESX 1.1, 1.2 and v1-final scripts to ESX Legacy, from getSharedObject and fxmanifest to mysql-async, xPlayer changes and events.
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.