Skip to main content
FrameworksCluster guide · 11 min read

How to Migrate a FiveM Server from ESX to QBCore

A practical, engineer-tested walkthrough for moving a live FiveM server from es_extended to qb-core, including database mapping, resource conversion, and the gotchas that break player data.

Migrate ESX to QBCore: FiveM Server Guide
Quick answer

To migrate from ESX to QBCore, stand up a fresh QBCore server alongside your live one, map ESX concepts to their QBCore equivalents (the client-side ESX.GetPlayerData becomes QBCore.Functions.GetPlayerData, the server-side ESX.GetPlayerFromId(source) becomes QBCore.Functions.GetPlayer(source), and the users table becomes the players table with a JSON charinfo column), then convert each resource to call QBCore exports and events instead of ESX ones. Migrate the database last with a script that maps ESX identifier rows into QBCore character rows. Plan for a full rebuild of any resource that touches money, jobs, or inventory, because the data shapes are not compatible by default.

On this page

Why teams move from ESX to QBCore

ESX and QBCore both give you the same core: a player loads in, the server knows who they are, they have money, a job, and an inventory. The difference is how each framework stores and exposes that data, and that difference is the entire migration. ESX grew up as the older, flatter system. QBCore is newer, leans on a structured charinfo and metadata model, and ships a more consistent export pattern.

Most teams switch for one of three reasons: the scripts they want are QBCore-only (a large share of newer FiveM releases ship QBCore-first), they want multicharacter support that feels native instead of bolted on, or they are tired of fighting ESX legacy quirks across the esx_* resource family. None of those reasons make the migration trivial. Treat it as a rebuild of your data layer, not a find-and-replace.

The honest scope

A small roleplay server with 15 to 25 resources and one or two custom scripts is realistically a 20 to 40 hour migration for one experienced developer. A large server with 150-plus resources, custom jobs, and a heavily modified economy is a multi-week project. The work is not hard line by line. It is the volume of small, correct changes that adds up, plus the testing.

Step 1: Map the concepts before you touch code

The single most useful thing you can do first is build a translation table in your head and on paper. Almost every ESX call has a QBCore equivalent, but the data underneath is shaped differently.

Mechanically, ESX exposes a global ESX object you grab with exports['es_extended']:getSharedObject(). QBCore exposes QBCore via exports['qb-core']:GetCoreObject(). From there the method names diverge. In ESX you write local xPlayer = ESX.GetPlayerFromId(source) and read xPlayer.getMoney(). In QBCore you write local Player = QBCore.Functions.GetPlayer(source) and read Player.PlayerData.money.cash.

Concrete example: paying a player 500 dollars cash. ESX is xPlayer.addMoney(500). QBCore is Player.Functions.AddMoney('cash', 500). Note QBCore forces you to name the money account (cash, bank, crypto), while ESX defaults to the main account. That naming requirement shows up everywhere and is the number one source of silent bugs.

Step 2: Understand the database differences

This is where data gets lost if you rush. Both frameworks use MySQL through oxmysql, but the table layout is not the same.

In ESX, the canonical player table is users, keyed by identifier (the license or steam identifier). Money lives in columns like accounts (a JSON blob) or sometimes flat columns. Job is stored as job and job_grade. Inventory, in modern ESX with ox_inventory, lives in a separate ox_inventory table or in the inventory column depending on your setup.

In QBCore, the canonical table is players, keyed by citizenid (a generated short ID like ABC12345) plus license. Identity lives in a JSON charinfo column (firstname, lastname, birthdate, gender, phone). Money lives in a JSON money column like {"cash":500,"bank":5000,"crypto":0}. Job lives in a JSON job column. Arbitrary extra state lives in metadata.

The practical consequence: one ESX users row maps to one or more QBCore players rows, and you must invent a citizenid and a charinfo for each, because ESX often has no first/last name split. Write a SQL or Lua migration script that reads each users row, parses the ESX accounts JSON, and writes a players row with a freshly generated citizenid, a charinfo built from whatever name data you have, and a money JSON assembled from the old accounts.

A realistic migration query shape

You will not get this in one clean INSERT ... SELECT because of the JSON reshaping. The reliable pattern is a one-time Lua script run inside a temporary resource that uses oxmysql to MySQL.query every users row, transforms each in Lua (where JSON is easy), and MySQL.inserts into players. Keep the original users table untouched until you have verified the new data. Never drop it on day one.

Step 3: Convert resources one family at a time

Do not convert everything at once. Group your resources and convert by family, testing each group before moving on.

Start with qb-core itself installed and booting clean. Then bring over the spine: spawn, multicharacter, and the HUD. QBCore ships qb-spawn, qb-multicharacter, and qb-hud, which replace whatever your ESX setup used for spawning and multicharacter (commonly esx_multicharacter plus the base spawnmanager) and your ESX HUD resource. These must work before anything else, because every other resource assumes a loaded PlayerData.

Next convert jobs. An ESX job script listens for esx:setJob and reads xPlayer.job.name. The QBCore equivalent uses QBCore.Functions.GetPlayer, reads Player.PlayerData.job.name, and fires QBCore:Client:OnJobUpdate. A police script like esx_policejob has a direct counterpart in qb-policejob; in most cases you replace the resource entirely rather than convert it line by line, because the community QBCore version is already built and maintained.

When to replace versus convert

For any common resource that has a maintained QBCore version (police, ambulance, banking, shops, garages), replace it. Converting esx_policejob by hand when qb-policejob exists is wasted effort. Reserve hand-conversion for your genuinely custom scripts, the ones that make your server yours. For those, the work is: swap the core object getter, rename every money and job call, and re-point every database query at the new tables.

Step 4: Handle the inventory carefully

Inventory is the highest-risk piece because it is where players keep value. The good news: many ESX and QBCore servers both run ox_inventory, which is framework-agnostic. If you are already on ox_inventory under ESX, you can keep it under QBCore because ox_inventory detects the active framework (esx, qb, or qbox) at startup based on which core resource is running. Once es_extended is gone and qb-core is started ahead of it, ox_inventory binds to the QBCore bridge on its own. That alone removes a huge chunk of risk.

If you are on the older qb-inventory or a stock ESX inventory, expect more work. Item definitions move from the ESX items database table to the QBCore qb-core/shared/items.lua file (or the ox_inventory data/items.lua). Usable items registered with ESX.RegisterUsableItem('bread', ...) become QBCore.Functions.CreateUseableItem('bread', ...). The item names should be kept identical across the move so player inventories survive.

Step 5: Update server.cfg and start order

The server.cfg start order matters more than people expect. QBCore must start before any resource that calls GetCoreObject. A correct block ensures oxmysql starts first, then qb-core, then shared dependencies like ox_lib and ox_inventory, then everything else.

A minimal correct ordering looks like: ensure oxmysql, ensure ox_lib, ensure qb-core, ensure ox_inventory, then your resource folders. Remove every ensure es_extended and ensure esx_* line as you retire those resources. Leaving a stray esx_* resource running will throw errors on boot and can crash dependent scripts. Search your server.cfg for esx and es_extended and confirm each removed line has a QBCore replacement actually started.

Step 6: Test against real player data

Do your final verification on a copy of production data, not an empty database. Boot the QBCore server pointed at a cloned database, then log in as several migrated players and check: correct cash and bank balances, correct job and grade, intact inventory, and a working character that can spawn. A migration that works on a fresh test character but corrupts a real one is the failure mode that actually hurts.

Keep the old ESX server bootable and the original users table intact for at least a week after you go live. Rollback capability is the cheapest insurance you can buy.

Performance and the long view

Neither framework is meaningfully faster than the other at the core level; both are thin Lua layers over the same FiveM server. Performance problems almost always live in the resources, not the framework. The migration is a good moment to drop resources you no longer use and to standardize on ox_lib, ox_inventory, and oxmysql, which are the modern shared foundation both ecosystems are converging on. A clean QBCore install running ten well-written resources will idle around 0.01 to 0.05 ms per script in the txAdmin resource monitor, the same as a clean ESX install.

Checklist
  • Stand up a fresh QBCore server on a separate port, not on top of the live one
  • Install and boot qb-core clean before adding anything else
  • Get oxmysql, ox_lib, and ox_inventory starting in the correct order
  • Bring over the spine: qb-multicharacter, qb-spawn, qb-hud
  • Build a concept map: every ESX call to its QBCore equivalent
  • Replace common resources (police, ambulance, banking) with maintained QBCore versions
  • Hand-convert only your genuinely custom scripts
  • Swap getSharedObject() for GetCoreObject() in every custom resource
  • Rename every money call to name the account (cash, bank, crypto)
  • Re-point custom database queries from users to players
  • Write a one-time migration script that maps users rows to players rows with generated citizenid and charinfo
  • Move item definitions into shared/items.lua or ox_inventory/data/items.lua, keeping item names identical
  • Remove every ensure es_extended and ensure esx_* line from server.cfg
  • Test against a clone of production data, not an empty database
  • Verify migrated players: money, job, grade, inventory, spawn
  • Keep the old ESX server and original users table for at least a week
Concept ESX (es_extended) QBCore (qb-core)
Get core object exports['es_extended']:getSharedObject() exports['qb-core']:GetCoreObject()
Get player (server) ESX.GetPlayerFromId(source) QBCore.Functions.GetPlayer(source)
Player identifier identifier (license/steam) citizenid + license
Player table users players
Add cash xPlayer.addMoney(500) Player.Functions.AddMoney('cash', 500)
Money storage accounts JSON blob money JSON (cash/bank/crypto)
Read job xPlayer.job.name Player.PlayerData.job.name
Job change event esx:setJob QBCore:Client:OnJobUpdate
Usable item ESX.RegisterUsableItem(name, cb) QBCore.Functions.CreateUseableItem(name, cb)
Item definitions items DB table shared/items.lua or ox_inventory/data/items.lua
Identity data flat / minimal charinfo JSON
Extra state varies per resource metadata JSON
Common mistakes

Mistake 1: Treating it as find-and-replace. Renaming ESX to QBCore does not work because the data shapes differ. Fix: convert calls and migrate data as two separate, deliberate passes.

Mistake 2: Forgetting to name the money account. Player.Functions.AddMoney(500) silently fails or errors because QBCore requires the account name. Fix: always pass 'cash', 'bank', or 'crypto' as the first argument.

Mistake 3: Dropping the users table too early. Once it is gone, a bad migration is unrecoverable. Fix: keep users untouched and the old server bootable for at least a week after go-live.

Mistake 4: Leaving stray esx_* resources in server.cfg. A single orphaned ESX resource throws boot errors and can crash dependents. Fix: grep server.cfg for esx and es_extended and confirm each line is removed or replaced.

Mistake 5: Changing item names during the inventory move. If bread becomes qb_bread, every existing player loses that item. Fix: keep item names byte-for-byte identical across the migration.

Mistake 6: Testing only on a fresh character. A migration can pass on a new character and still corrupt real player rows. Fix: always do final verification against a clone of production data.

The Quasar take

Having shipped and supported scripts across tens of thousands of installs, the Quasar view is blunt: most servers should standardize on ox_inventory, ox_lib, and oxmysql regardless of which framework they pick, because that shared foundation is where both ESX and QBCore are actually converging. If you are already on ox_inventory, your ESX-to-QBCore migration is half as scary as the forums make it sound. The framework label matters far less than the discipline of your data migration and the quality of the ten resources you actually run. Do not migrate to chase a trend. Migrate because a specific set of scripts you want is QBCore-first, and accept that it is a data project, not a rename.

Can I migrate from ESX to QBCore without losing player data?
Yes, but only with a deliberate database migration. You write a one-time script that maps each ESX users row to a QBCore players row, generating a citizenid and charinfo and rebuilding the money JSON from the old accounts. Keep the original users table until the new data is verified.
How long does an ESX to QBCore migration take?
For a small server of 15 to 25 resources, plan 20 to 40 hours for one experienced developer. A large server with 150-plus resources and a custom economy is a multi-week project. The difficulty is the volume of small correct changes plus testing, not any single hard step.
Do I have to rewrite every resource by hand?
No. For common resources like police, ambulance, banking, and garages, replace them with the maintained QBCore versions instead of converting. Reserve hand-conversion for your genuinely custom scripts.
Will my ox_inventory carry over?
If you already run ox_inventory under ESX, you can keep it under QBCore. ox_inventory detects the active framework at startup based on which core resource is running, so once es_extended is removed and qb-core starts first, it binds to the QBCore bridge automatically. Keep item names identical so existing player inventories survive the move.
What breaks most often during the migration?
Money calls that forget to name the account, stray esx_ resources left in server.cfg, and item renames that wipe player inventories. All three are avoidable with careful review before go-live.
Is QBCore faster than ESX?
Not meaningfully at the framework level. Both are thin Lua layers over the same FiveM server. Performance lives in the resources you run, so use the migration as a chance to drop unused scripts and standardize on the ox stack.

Ready for the next step?

Stop guessing. Get a concrete plan for your server and move with confidence.

Written by
Kishi · FiveM Coach
Part of the FiveM Coach by Quasar team. We help server owners launch, fix, grow, and monetize stable RP cities.