A practical guide to how to migrate from esx to qbcore on your fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.
Introduction
This guide walks a server operator through migrating a FiveM server from ESX to QBCore with emphasis on architecture, security, implementation choices, testing, and safe deployment. It assumes you maintain production players and want to minimize downtime while preserving player data and gameplay behavior. The migration strategy below favors incremental, reversible steps and strict server-side validation. If you need tooling or a control dashboard during migration, consider external tools or platforms that integrate with your workflow; see the Stellar AI app for project management and tracking: https://trystellarai.com/app.
Architecture Differences: ESX vs QBCore
Understanding the core architectural differences helps you decide how to map resources and APIs.
- Player object and functions: ESX exposes xPlayer and exported functions; QBCore exposes player objects via QBCore.Functions.GetPlayer and a callback system (QBCore.Functions.CreateCallback). QBCore tends to centralize functionality under QBCore.Functions and uses standardized events and exports.
- Database layer: Both setups use MySQL (oxmysql/ghmattimysql/mysql-async). Modern QBCore installs commonly use oxmysql and pair well with ox_lib utilities.
- Libraries: QBCore servers commonly include ox_lib for UI and utility features; ESX servers historically use different helper resources. Expect to replace or adapt client-side UI code to work with ox_lib or native qb-target / qb-menu patterns.
- Resource naming: QBCore organizes core resources (qb-core, qb-banking, qb-inventory). Plan namespace changes and ensure no conflicting resource names.
Planning the Migration
Establish a migration plan with prioritized items: player persistence, economy (money, bank), jobs, inventory, vehicles, and third-party scripts (garages, jobs, shops).
- Inventory current resources and dependencies: list ESX scripts, their versions, and what DB tables they use.
- Back up your full database and resource folder (including .sql dumps and resource zips).
- Choose a migration strategy: big-bang replacement, staged conversion, or adapter/wrapper layer.
- Set up a staging server that mirrors production but with a copy of the database. Never test on live without a rollback plan.
Data Migration: Mapping Schemas and Ensuring Integrity
Data is the most critical piece. Use an explicit mapping table to convert ESX schema to QBCore equivalents and validate identifiers.
| ESX Table/Concept | QBCore Equivalent | Migration Action |
|---|---|---|
| users / users table (money, bank) | players or player tables in qb-core | Map money fields to player financial properties; migrate using an explicit UPDATE or insert to qb_core players table. Ensure identifier fields (license, steam) match. |
| owned_vehicles | player_vehicles or qb-vehicles | Convert vehicle JSON schemas and ensure plate collisions are handled. Verify ownership queries after import. |
| inventory (esx_inventory) | qb-inventory or ox_inventory | Normalize item identifiers and quantity fields. Test stack limits and weight if used. |
| job assignments | qb-core job system | Map ESX job names and grades to QBCore job entries. Update DB rows to reflect new job identifiers. |
Tips:
- Perform a dry-run import into a staging DB and run integrity checks (count mismatches, missing identifiers).
- Preserve old player identifiers in a migration table to allow rollback.
- Keep transactions atomic where multiple tables must change simultaneously. Use SQL transactions via oxmysql where available.
Implementation Choices: Strategies and Patterns
There are three common approaches to actually replacing ESX with QBCore:
- Adapter/Compatibility Layer — create lightweight server-side shims that implement ESX exports using QBCore internals. This allows 3rd-party ESX scripts to keep functioning temporarily while you convert them. Use this for low-risk, low-traffic servers where immediate behavior parity is required.
- Gradual Resource Conversion — convert scripts in priority order (e.g., economy first, then jobs, then misc resources). Use feature flags and turn resources off/on per job to test in isolation.
- Full Replacement — deploy qb-core and converted resources in a maintenance window. Require more planning but faster completion.
Server validation code example (FiveM / QBCore): never trust client inputs — always validate server-side.
RegisterServerEvent('myshop:buyItem')
AddEventHandler('myshop:buyItem', function(itemName, amount)
local src = source
local xPlayer = QBCore.Functions.GetPlayer(src)
if not xPlayer then return end
-- Server-side lookup for item price and availability
local itemDef = Config.ShopItems[itemName]
if not itemDef then return end
local price = itemDef.price * tonumber(amount or 1)
-- Validate player can afford the purchase on server
if xPlayer.PlayerData.money.cash >= price then
-- Deduct and grant item server-side
xPlayer.Functions.RemoveMoney('cash', price)
xPlayer.Functions.AddItem(itemName, tonumber(amount))
TriggerClientEvent('QBCore:Notify', src, 'Purchase complete', 'success')
else
TriggerClientEvent('QBCore:Notify', src, 'Insufficient funds', 'error')
end
end)
Key patterns:
- Resolve item metadata on the server; do not accept client-side price or ID fields.
- Use QBCore.Functions.GetPlayer and manipulate PlayerData only server-side.
- Use callbacks for synchronous client-server queries when needed (QBCore.Functions.CreateCallback).
Security and Server Validation
Security is critical during and after migration. Follow these rules:
- Never trust client input. Always revalidate items, quantities, and prices on the server.
- Use server-side rate limiting for expensive operations (e.g., item spawns, bank transfers) and enforce per-player cooldowns.
- Validate identifiers (license, steam) present in DB rows when reconciling migrated data. Ensure no duplicates or mismatches that could cause account takeover.
- Secure remote calls: prefer server callbacks or exports rather than raw RegisterNetEvent handlers without validation.
- Plan for rollback: keep migration scripts reversible by storing original rows in a migration log table.
Testing and QA
Testing should cover functional, integration, and security tests. Set up test cases that simulate typical player flows and edge cases.
- Automated script test: spawn players, perform common actions, assert DB state changes.
- Manual QA: enlist a small group of trusted players to validate economy and jobs on a staging server.
- Security test: attempt invalid client payloads and verify server rejects them consistently.
Practical migration checklist (use this during each resource conversion):
| Step | Action | Pass Criteria |
|---|---|---|
| Backup | Export DB and resource files | Complete dump verified on separate machine |
| Staging Import | Import data to staging DB | No integrity errors; user counts match |
| Adapter Test | Run ESX script against QBCore adapter | Script functions as expected in staging |
| Functional QA | Perform transactions and job actions | All flows complete and DB consistent |
| Security Check | Test invalid client inputs | Server rejects invalid requests and logs attempts |
Deployment and Rollout Strategies
Minimize player disruption by using careful rollout techniques.
- Blue/Green or Staged Rollout: Run QBCore on a copy of the server, redirect a subset of players to test, then switch traffic after monitoring.
- Feature flags: Toggle new functionality on only for testing accounts before enabling globally.
- Maintenance window: For a full replacement, schedule a window and notify players in advance. Ensure backups are accessible and that rollback scripts are ready.
After deployment, continue monitoring logs for unexpected validation failures and re-run migration scripts if any data mismatches appear. For extra observability, you can integrate external dashboards; one option that teams use to coordinate deployment tasks and track issues is the Stellar AI platform: https://trystellarai.com/app.
Maintenance and Future-Proofing
After migration, lock in practices that keep the server healthy and easier to maintain:
- Keep QBCore and ox_lib up to date and follow changelogs before upgrading production. Test upgrades in staging first.
- Document any adapters or compatibility layers you created; these are technical debt and should be prioritized for eventual removal.
- Standardize server-side validation helpers and centralize them where possible so future resources reuse secure patterns.
- Keep a migration log table that records migrations, scripts run, and their checksums to audit later.
For further reading on operational best practices and community updates, see the project blog: https://trystellarai.com/blog.
Roblox-Specific Considerations (If Relevant)
If your team also develops for Roblox, apply similar server-authority principles. A few concrete rules:
- Server Authority: All game state changes must be applied on the server. Do not perform authoritative state updates from client scripts.
- RemoteEvents & RemoteFunctions: Use RemoteEvents or RemoteFunctions for client-server communication. Validate every request on the server. Example handler:
local Remote = game.ReplicatedStorage:WaitForChild("PurchaseEvent")
Remote.OnServerEvent:Connect(function(player, itemId, amount)
-- Validate types and player state
if typeof(itemId) ~= "string" or typeof(amount) ~= "number" then return end
-- Validate item exists on server
local itemDef = itemConfig[itemId]
if not itemDef then return end
-- Validate currency, deduct server-side, then grant
local leaderstats = player:FindFirstChild("leaderstats")
if leaderstats and leaderstats.Cash.Value >= itemDef.price * amount then
leaderstats.Cash.Value = leaderstats.Cash.Value - itemDef.price * amount
-- grant item server-side
end
end)
DataStore Failure Handling: Always use pcall for datastore operations and implement retries with backoff. Never assume GetAsync/UpdateAsync will always succeed. Example pattern:
local function safeGet(datastore, key)
local attempts = 0
while attempts <= 5 do
local ok, result = pcall(function()
return datastore:GetAsync(key)
end)
if ok then
return true, result
end
attempts = attempts + 1
wait(2 ^ attempts * 0.1) -- exponential backoff
end
return false, nil
end
Monitoring, Logging, and Incident Response
After the migration, set up monitoring for key signals: unexpected money spikes, rapid inventory changes, or repeated validation errors. Log rejected client payloads with context (player id, offending payload, server-side rule triggered) to diagnose attempted exploits. Maintain a simple incident playbook and ensure backups exist before any emergency fix is applied.