# ESX vs QBCore (/docs/guides/esx-vs-qbcore)

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.

<Callout type="info" title="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](https://qbcore.net). QBCore changes over time, so verify QBCore specifics there before you make a decision.
</Callout>

## Quick comparison [#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 [#architecture]

### ESX Legacy [#esx-legacy]

ESX Legacy is split into a core and optional resources:

- **`es_extended`** is the framework core. It loads players, manages accounts, jobs, groups, the default inventory, commands, callbacks and paychecks.
- **`esx_lib`** is a shared library that `es_extended` depends 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_context` and `esx_notify` handle character creation and UI.
- **Addons** like `esx_policejob`, `esx_society`, `esx_billing` and `esx_garage` add 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](/docs/guides/create-a-job) and [Add items](/docs/guides/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]

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](https://qbcore.net) for the current file layout, because it has changed between versions.

### What this means in practice [#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 [#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:

```lua title="server/main.lua"
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](/docs/guides/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](/docs/guides/performance).

## Ecosystem [#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](/docs/guides/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](/docs/guides/ox-inventory).

<Callout type="warn" title="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.
</Callout>

## Performance [#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 `resmon` and 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](/docs/guides/performance).

## Learning curve [#learning-curve]

- **ESX** has the most tutorials and forum answers of any FiveM framework, but many of them are outdated (`esx:getSharedObject` events, `mysql-async`). Learning to recognize old patterns is part of learning ESX. The core API is small and consistent: one `ESX` object, one `xPlayer` object.
- **QBCore** documentation and tutorials are mostly newer. If you are used to its `Functions` naming, 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 [#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:

<Steps>
<Step>
### Inventory your scripts [#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.
</Step>
<Step>
### Map your data [#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](/docs/guides/database-schema).
</Step>
<Step>
### Write and test a one-off migration [#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>`.
</Step>
<Step>
### Run both servers side by side [#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.
</Step>
</Steps>

## How to decide [#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](https://qbcore.net).
- If you are undecided, list the 10 scripts your server cannot live without and check which framework they support.

## Common issues [#common-issues]

- **"WE STOPPED A RESOURCE THAT WILL BREAK ESX"**: you have `qb-core` or another conflicting resource in your `server.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 [#related]

- [Introduction to ESX Legacy](/docs)
- [Installation](/docs/getting-started/installation)
- [Convert old ESX scripts](/docs/guides/convert-old-esx-scripts)
- [xPlayer reference](/docs/es_extended/server/xplayer)
- [Database schema](/docs/guides/database-schema)
