Converting FiveM Scripts: ESX, QBCore & QBOX Framework Guide
Code-first guide to converting FiveM scripts between ESX, QBCore, and QBOX. Includes tri-way API mappings, adapter code, SQL migration, and testing checklists.

Introduction to This is a code‑first, no‑fluff easy converting FiveM
This is a code‑first, no‑fluff easy converting FiveM scripts guide that shows you exactly how to Convert FiveM Scripts between ESX, QBCore, QBOX (qbx_core), and framework‑agnostic (standalone) setups. You’ll get tri‑way API mappings, adapter code, SQL migration steps (mysql‑async → oxmysql), QBOX specifics, testing checklists, and production hardening tips.
TL;DR — Migration in 10 Steps
- Snapshot & staging: Back up DB/files. Spin up a local test server with the target framework.
- Identify dependencies: inventory, target, menu/UI, callbacks, DB layer, core access.
- Pin versions & order: oxmysql → ox_lib → framework (es_extended / qb-core / qbx_core) → your resources.
- Read the code path: Find all player, money, job/duty, inventory, callback/event, and DB calls.
- Map APIs: Use the Rosetta tables below to map ESX ↔ QBCore ↔ QBOX.
- Bridge it: Add a bridge.lua adapter that auto‑detects the framework and normalizes calls.
- Convert callbacks: ESX/QB callbacks → QBOX/ox_lib
lib.callback.*when needed. - Migrate DB: Move identifiers, accounts, items, vehicles; switch to oxmysql
*.awaitstyle. - Harden events: Validate
source, bounds, groups/jobs, ownership; never trust client. - QA & release: Run the checklists; tag, changelog, and rollback plan.
Are you a developer that is looking for an Adapter solution? Check out our free adapter script here
1) Frameworks 101: ESX vs QBCore vs QBOX vs Standalone
Architectural overview
Category
ESX
QBCore
QBOX
Architecture / Core Access
exports['es_extended']:getSharedObject() → ESX
exports['qb-core']:GetCoreObject() → QBCore
No global core object; use exports.qbx_core:* for player, groups, and notifications
Player Object
ESX.GetPlayerFromId(src) → xPlayer
QBCore.Functions.GetPlayer(src) → Player
exports.qbx_core:GetPlayer(src) → Player with PlayerData
Identifiers
Historically identifier (license/steam)
citizenid as primary key
citizenid as primary key
Money Handling
xPlayer.addMoney() / \addAccountMoney(‘bank’
‘black_money’)\
\Player.Functions.AddMoney(‘cash’
Jobs & Duty
xPlayer.job.name, .job.grade
Player.PlayerData.job.name, .grade.level, OnDuty
Job/group helpers via exports.qbx_core:* (group checks, duty setters)
Inventory
ESX default / `ox_inventory` / `qb-inventory` (recommended: `ox_inventory`)
qb-inventory / ox_inventory
ox_inventory recommended
Callbacks
ESX.RegisterServerCallback / ESX.TriggerServerCallback
QBCore.Functions.CreateCallback / TriggerCallback
Prefer ox_lib callbacks (lib.callback.register/await) or QBOX exports
Database Layer
Old: mysql-async → Now: oxmysql (MySQL.query.await, MySQL.update.await)
oxmysql
oxmysql
UI – Notifications
ESX Notify
QB Notify
QBOX notify helpers or lib.notify
UI – Menus
esx_menu_default / ox_lib:registerContext
qb-menu / ox_lib:registerContext
ox_lib:registerContext / lib.inputDialog
UI – Target System
esx_target / ox_target
qb-target / ox_target
ox_target recommended
Where QBOX differs
- Resource name and exports:
qbx_core(noGetCoreObject). - Leans on ox_lib (callbacks, notify) and oxmysql by default.
- Built‑ins for multicharacter/queue/groups; opinionated helpers for jobs/duty.

2) Preflight: Environment & Tooling
- Server: current FXServer artifacts, Lua 5.4, CFX
ensureorder - Order:
ensure oxmysql→ensure ox_lib→ensure framework→ensure yourResource - Editor: VS Code + Lua LS, stylua; optional ripgrep for quick pattern scans
- Test setup: clean database snapshot; verbose logging; small seeded data set
- Search patterns you’ll use:
-
ESX\.xPlayer\.QBCore\.PlayerDataqbx_coreexports\.ox_inventorymysql%-asyncMySQL\.
-
## 3) Rosetta Stone: ESX ↔ QBCore ↔ QBOX (Tri‑way Mapping)
Copy/paste friendly reference
Copy/paste friendly reference. Use it to replace calls or wire your adapter.
Core & Player
Concept
ESX
QBCore
QBOX
Get core
`exports['es_extended']:getSharedObject()`
`exports['qb-core']:GetCoreObject()`
_(n/a — use exports)_
Get player (src)
`ESX.GetPlayerFromId(src)`
`QBCore.Functions.GetPlayer(src)`
`exports.qbx_core:GetPlayer(src)`
Get by citizenid
_(custom query)_
`QBCore.Functions.GetPlayerByCitizenId(id)`
`exports.qbx_core:GetPlayerByCitizenId(id)` _(if present)_
Identifiers
Concept
ESX
QBCore
QBOX
Primary
`identifier` (license/steam)
citizenid
citizenid
Crosswalk
table `id_map(identifier, citizenid)`
same
same
Money
Action
ESX
QBCore
QBOX
Add cash
`xPlayer.addMoney(a)`
`Player.Functions.AddMoney('cash', a)`
`Player.PlayerData.money.cash += a` _(via adapter + save)_
Add bank
`xPlayer.addAccountMoney('bank', a)`
`Player.Functions.AddMoney('bank', a)`
`Player.PlayerData.money.bank += a` _(adapter + save)_
Add dirty
`xPlayer.addAccountMoney('black_money', a)`
item or extra account (server‑defined)
map to item/alt wallet (e.g., `crypto`) _(adapter choice)_
Jobs, Duty, Groups
Concept
ESX
QBCore
QBOX
Read job
xPlayer.job.name, .grade
Player.PlayerData.job.name, .grade.level
Player.PlayerData.job.* + group helpers
On duty
_(varies)_
Player.PlayerData.job.onduty
SetJobDuty/group helpers via exports
Group check
_(add-on)_
_(add-on)_
`exports.qbx_core:HasGroup(src, group)` _(example)_
Inventory
Action
ESX
QBCore
QBOX
Add item
`xPlayer.addInventoryItem(n, c)`
`Player.Functions.AddItem(n, c, ...)`
prefer ox_inventory export via adapter
ox_inventory
exports.ox_inventory:*
exports.ox_inventory:*
exports.ox_inventory:*
Callbacks
Concept
ESX
QBCore
QBOX
Server register
ESX.RegisterServerCallback
QBCore.Functions.CreateCallback
`lib.callback.register` (ox_lib)
Client trigger
ESX.TriggerServerCallback
QBCore.Functions.TriggerCallback
lib.callback.await
DB Layer
Concept
ESX/QB (legacy)
oxmysql (target)
Fetch
`MySQL.Async.fetchAll(sql, params, cb)`
`local rows = MySQL.query.await(sql, {p1, ...})`
Update
`MySQL.Async.execute(sql, params, cb)`
`local aff = MySQL.update.await(sql, {p1, ...})`
UI / Notify / Target
Concept
ESX
QBCore
QBOX
Notify
`ESX.ShowNotification(msg)`
`QBCore.Functions.Notify(msg, type)`
`exports.qbx_core:Notify(...)` or `lib.notify({...})`
Menu
_(varies)_
qb-menu
ox_lib:registerContext / inputs
Target
_(add-on)_
qb-target
ox_target
Tip: favor ox_lib, ox_inventory, ox_target to stay neutral across frameworks.
## 4) Tutorial A — ESX → QBCore (Hands‑on)
Goal: Convert a simple shop resource from ESX to QBCore.
Step 1 — Dependencies
- Ensure
oxmysql,ox_lib,qb-coreare started. - If the script uses ESX inventory, migrate to ox_inventory (recommended) or map to QB inventory.
Step 2 — Detect framework & add a bridge
Create bridge.lua to normalize players/money/inventory:
-- bridge.lua (ESX/QB/QBOX adapter)
local M = {}
local fw
CreateThread(function()
if GetResourceState('qbx_core') == 'started' then
fw = 'qbx'
elseif GetResourceState('qb-core') == 'started' then
fw = 'qb'
elseif GetResourceState('es_extended') == 'started' then
fw = 'esx'
else
fw = 'none'
end
end)
function M.getFramework()
return fw
end
function M.getPlayer(src)
if fw == 'qbx' then
return exports.qbx_core:GetPlayer(src)
elseif fw == 'qb' then
return exports['qb-core']:GetCoreObject().Functions.GetPlayer(src)
elseif fw == 'esx' then
return exports['es_extended']:getSharedObject().GetPlayerFromId(src)
end
end
local function saveQbox(src)
if fw == 'qbx' and exports.qbx_core and exports.qbx_core.Save then
exports.qbx_core:Save(src)
end
end
function M.addMoney(src, account, amount)
local p = M.getPlayer(src)
if not p or type(amount) ~= 'number' then return false end
if fw == 'qb' then
return p.Functions.AddMoney(account, amount)
elseif fw == 'esx' then
if account == 'cash' then
return p.addMoney(amount)
elseif account == 'bank' then
return p.addAccountMoney('bank', amount)
elseif account == 'black' or account == 'black_money' then
return p.addAccountMoney('black_money', amount)
end
elseif fw == 'qbx' then
local money = p.PlayerData and p.PlayerData.money or {}
account = account == 'black' and 'crypto' or account -- example mapping
money[account] = (money[account] or 0) + amount
saveQbox(src)
return true
end
return false
end
function M.removeMoney(src, account, amount)
return M.addMoney(src, account, -math.abs(amount))
end
function M.addItem(src, name, count, meta)
if GetResourceState('ox_inventory') == 'started' then
return exports.ox_inventory:AddItem(src, name, count, meta)
elseif fw == 'qb' then
return exports['qb-inventory']:AddItem(src, name, count, false, meta)
elseif fw == 'esx' then
local p = M.getPlayer(src)
return p and p.addInventoryItem(name, count)
elseif fw == 'qbx' then
return exports.ox_inventory:AddItem(src, name, count, meta)
end
end
function M.notify(src, msg, ntype)
if fw == 'qb' then
TriggerClientEvent('QBCore:Notify', src, msg, ntype or 'primary')
elseif fw == 'esx' then
TriggerClientEvent('esx:showNotification', src, msg)
elseif fw == 'qbx' then
if exports.qbx_core and exports.qbx_core.Notify then
exports.qbx_core:Notify(src, msg, ntype or 'info')
else
lib.notify(src, { title = 'Notice', description = msg })
end
else
print(('notify(%s): %s'):format(src, msg))
end
end
return M
Step 3 — Convert callbacks
- ESX → QB: replace
ESX.RegisterServerCallbackwithQBCore.Functions.CreateCallback. - Client uses
QBCore.Functions.TriggerCallback.
-- server.lua (callback)
local bridge = require 'bridge'
local function getPrice(item)
return 100
end
if GetResourceState('qb-core') == 'started' then
local QBCore = exports['qb-core']:GetCoreObject()
QBCore.Functions.CreateCallback('shop:getPrice', function(source, cb, item)
cb(getPrice(item))
end)
else
-- fallback for QBOX/ESX via ox_lib
lib.callback.register('shop:getPrice', function(source, item)
return getPrice(item)
end)
end
-- client.lua (callback)
if GetResourceState('qb-core') == 'started' then
local QBCore = exports['qb-core']:GetCoreObject()
QBCore.Functions.TriggerCallback('shop:getPrice', function(price)
print('price', price)
end, 'bread')
else
local price = lib.callback.await('shop:getPrice', false, 'bread')
print('price', price)
end
Step 4 — Money & inventory paths
- Replace
xPlayer.addAccountMoneywithPlayer.Functions.AddMoney(for QB) or adapter. - Standardize all inventory changes through the adapter.
### Step 5 — Events (server‑side security)
RegisterNetEvent('shop:buy', function(item, amount)
local src = source
if type(item) ~= 'string' or type(amount) ~= 'number' then return end
if amount < 1 or amount > 10 then return end
local p = bridge.getPlayer(src)
if not p then return end
local price = 100 * amount
if not bridge.removeMoney(src, 'cash', price) then return end
bridge.addItem(src, item, amount)
bridge.notify(src, ('Bought %dx %s'):format(amount, item), 'success')
end)
### Step 6 — DB migration (identifiers, accounts)
- Create a crosswalk
id_map(identifier, citizenid). - Populate
citizenidfor QB from ESXuserstable via rule of choice (existing column or generated).
-- Example: create crosswalk and backfill (customize to your schema)
CREATE TABLE IF NOT EXISTS id_map (
identifier VARCHAR(64) PRIMARY KEY,
citizenid VARCHAR(64) NOT NULL UNIQUE
);
-- Suppose you generated new citizenids and stored them earlier
INSERT IGNORE INTO id_map(identifier, citizenid)
SELECT u.identifier, u.citizenid FROM users u WHERE u.citizenid IS NOT NULL;
mysql‑async → oxmysql
mysql‑async → oxmysql
-- before (mysql-async)
MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {['@id'] = identifier}, function(rows) ... end)
-- after (oxmysql)
local rows = MySQL.query.await('SELECT * FROM users WHERE identifier = ?', { identifier })
Step 7 — QA
- Spawn items, buy/sell, ensure balances change correctly.
- Verify callbacks return under load.
- Check that inventory metadata is preserved.
## 5) Tutorial B — QBCore → ESX (Hands‑on)
Key caveats:
- ESX uses accounts for
bank/black_money. Port QB’s dirty‑money item (if used) to ESX account or keep it as an item. - Job schema differs (grade vs level). Map carefully.
Example: Money mapping via adapter
-- in bridge.lua, when fw == 'esx'
function M.qbToEsxMoney(account)
if account == 'cash' then return 'cash' end
if account == 'bank' then return 'bank' end
if account == 'black' or account == 'black_money' then return 'black_money' end
return account
end
### Example: Callback conversion (server)
if GetResourceState('es_extended') == 'started' then
local ESX = exports['es_extended']:getSharedObject()
ESX.RegisterServerCallback('garage:getVehicles', function(source, cb)
local src = source
local p = bridge.getPlayer(src)
local rows = MySQL.query.await('SELECT * FROM owned_vehicles WHERE owner = ?', { p.identifier })
cb(rows)
end)
end
Testing checklist
- Job set/promotions persist.
- Bank/dirty balances mutate as expected.
- Vehicle ownership and plate formats validated.
## 6) Tutorial C — QBCore ↔ QBOX (Hands‑on)
QBCore → QBOX
- Replace
QBCore.Functions.*withexports.qbx_core:*or ox_lib callbacks. - Player:
QBCore.Functions.GetPlayer(src)→exports.qbx_core:GetPlayer(src). - Notify:
QBCore.Functions.Notify→exports.qbx_core:Notifyorlib.notify. - Duty/groups: use QBOX exports (
SetJobDuty,HasGroup, etc.).
QBOX → QBCore
- Replace
exports.qbx_core:*withQBCore.Functions.*equivalents or your adapter. - Reintroduce QB callbacks and menu/target equivalents as needed.
Before/After snippets
-- Before (QB notify)
QBCore.Functions.Notify('Hello', 'success')
-- After (QBOX)
exports.qbx_core:Notify(source, 'Hello', 'success')
-- or
lib.notify(source, { title = 'Hello', description = 'Welcome', type = 'success' })
-- Before (QB get player)
local Player = QBCore.Functions.GetPlayer(src)
-- After (QBOX)
local Player = exports.qbx_core:GetPlayer(src)
## 7) Tutorial D — Framework → Standalone with Adapters
Write your scripts once and run them anywhere:
- Create a bridge exposing a stable API:
getPlayer,getIdentifier,add/removeMoney,add/removeItem,notify,hasGroup,onDuty,callbackswrapper. - Detect framework at runtime (
qbx_core→qb-core→es_extended→none). - Use ox_lib for callbacks and notifications when no direct framework helper exists.
Minimal standalone fallback
-- when fw == 'none'
function M.getPlayer(src)
-- implement a minimal table or reject actions gracefully
return { id = src }
end
function M.notify(src, msg)
print(('notify(%s): %s'):format(src, msg))
end
## 8) Performance & Security (Production Hardening)
### Server‑side validation (never trust client)
- Validate types and bounds on every event.
- Check ownership, cooldowns, distance (if relevant), job/group.
- Ensure money can’t go negative; clamp amounts.
- Compare price server‑side; don’t accept client‑provided totals.
Event structure
- Use one server event per action; do not expose raw inventory/money functions to clients.
- Prefer callbacks for request/response flows.
DB & performance
- Switch to oxmysql await APIs; batch writes; avoid per‑tick queries.
- Index frequently queried columns (
citizenid,identifier,plate). - Cache config/price lists in memory; export them to clients once, then diff on change.
- Use
GlobalStatesparingly; avoid hot‑loop updates.
## 9) Common Pitfalls & Debugging Playbook
- Assuming
GetCoreObjectexists on QBOX → it doesn’t; useexports.qbx_core:*. - Callbacks never return after migrating to QBOX → ESX/QB callbacks aren’t registered; switch to
lib.callback.register/await. - Dirty money semantics differ → decide item vs account vs alt wallet and standardize in your adapter.
- Mixed inventory assumptions → normalize on
ox_inventory. - Start order issues →
oxmysql→ox_lib→ framework → your resources. - Vehicle metadata drift → ensure JSON columns and plate formats match target framework.
## 10) fxmanifest.lua Best Practices
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
shared_scripts {
'@ox_lib/init.lua',
'bridge.lua',
'config.lua'
}
client_scripts {
'client/*.lua'
}
server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/*.lua'
}
escrow_ignore {
'bridge.lua', 'config.lua'
}
-- dependencies (pick what you use): ox_lib, oxmysql, ox_inventory, qb-core OR qbx_core OR es_extended
- Declare Lua 5.4 (
lua54 'yes'). - Keep
escrow_ignoreminimal; never attempt escrow bypass.
## 11) Data Migration: SQL Snippets (Examples)
Adjust table/column names to your schema
Adjust table/column names to your schema. Always run on a copy first.
Identifiers: ESX → QB/QBOX
-- Add citizenid to ESX users if missing
ALTER TABLE users ADD COLUMN IF NOT EXISTS citizenid VARCHAR(64);
-- Populate using a deterministic generator or an existing column
UPDATE users SET citizenid = LOWER(SUBSTRING(MD5(CONCAT(identifier,'-QB')),1,10))
WHERE citizenid IS NULL;
-- Create crosswalk
CREATE TABLE IF NOT EXISTS id_map (
identifier VARCHAR(64) PRIMARY KEY,
citizenid VARCHAR(64) NOT NULL UNIQUE
);
INSERT IGNORE INTO id_map(identifier, citizenid)
SELECT identifier, citizenid FROM users WHERE citizenid IS NOT NULL;
Accounts/Money
-- Example: convert ESX bank balances to QB money table (if your QB schema stores it separately)
INSERT INTO player_money (citizenid, account, amount)
SELECT m.citizenid, 'bank', a.money
FROM (
SELECT u.citizenid, SUM(CASE WHEN account = 'bank' THEN money ELSE 0 END) AS money
FROM user_accounts ua
JOIN users u ON u.identifier = ua.identifier
GROUP BY u.citizenid
) a
JOIN users m ON m.citizenid = a.citizenid
ON DUPLICATE KEY UPDATE amount = VALUES(amount);
Vehicles
-- Normalize plates to target format (example: 8 chars upper)
UPDATE owned_vehicles SET plate = UPPER(LEFT(plate,8));
-- Ensure JSON metadata columns are valid
UPDATE owned_vehicles SET mods = JSON_MERGE_PATCH('{}', mods) WHERE JSON_VALID(mods) = 0;
## 12) Adapters You Can Reuse (Starter)
Drop this into
bridge.luaand expand per your needs. It auto‑detects ESX/QB/QBOX and exposes a stable API.
local M = {}
local fw = 'none'
CreateThread(function()
if GetResourceState('qbx_core') == 'started' then fw = 'qbx'
elseif GetResourceState('qb-core') == 'started' then fw = 'qb'
elseif GetResourceState('es_extended') == 'started' then fw = 'esx' end
end)
function M.framework() return fw end
function M.player(src)
if fw == 'qbx' then return exports.qbx_core:GetPlayer(src)
elseif fw == 'qb' then return exports['qb-core']:GetCoreObject().Functions.GetPlayer(src)
elseif fw == 'esx' then return exports['es_extended']:getSharedObject().GetPlayerFromId(src) end
end
function M.identifier(src)
local p = M.player(src); if not p then return nil end
if fw == 'qb' or fw == 'qbx' then
return p.PlayerData and p.PlayerData.citizenid
elseif fw == 'esx' then
return p.identifier
end
end
function M.hasGroup(src, group)
if fw == 'qbx' and exports.qbx_core and exports.qbx_core.HasGroup then
return exports.qbx_core:HasGroup(src, group)
end
return false
end
function M.notify(src, msg, ntype)
if fw == 'qb' then
TriggerClientEvent('QBCore:Notify', src, msg, ntype or 'primary')
elseif fw == 'esx' then
TriggerClientEvent('esx:showNotification', src, msg)
elseif fw == 'qbx' then
if exports.qbx_core and exports.qbx_core.Notify then
exports.qbx_core:Notify(src, msg, ntype or 'info')
else
lib.notify(src, { description = msg })
end
end
end
return M
## 13) Final Checklists (Copy & Ship)
### A) Discovery & Preflight
- Snapshot DB/files; create a staging server
- Pin artifacts and resource versions; define start order
- Inventory/target/menu dependencies listed
- Decide identifier strategy (citizenid crosswalk)
### B) Code Audit
- Grep for ESX/QB/QBOX/ox_*/mysql‑async
- List all money/inventory/job/duty calls
- List callbacks and server/client events
### C) Mapping & Design
- Choose adapter surface (player, ids, money, items, notify, callbacks)
- Decide dirty‑money strategy (account vs item vs alt wallet)
- Favor ox_lib/ox_inventory/ox_target
### D) Data Migration
- Build citizenid crosswalk
- Convert accounts/money
- Normalize vehicle metadata and plates
- Switch to oxmysql await calls
### E) QA Testing
- Event security: types/bounds/ownership/cooldowns
- Callbacks: return values under load
- Inventory metadata preserved
- Jobs/duty/group logic matches design
### F) Release & Rollback
- Tag release; changelog
- Keep pre‑migration snapshot for 7–14 days
- Monitor errors/latency; index hot queries
## 14) FAQ (with JSON‑LD Schema)
Q: Can I port QBCore scripts to QBOX without a full
Q: Can I port QBCore scripts to QBOX without a full rewrite?
A: With adapters and ox_lib callbacks, most scripts need only call remapping.
Q: What do I do with black/dirty money?
**A:** Standardize in your adapter: ESX → `black_money` account; QB → item or extra account; QBOX → item or alt wallet (e.g., crypto). Keep one strategy project‑wide.
Q: Why do callbacks hang after moving to QBOX?
A: ESX/QB callbacks won’t fire on QBOX. Use lib.callback.register/await.
Q: Best way to migrate mysql‑async?
**A:** Replace with **oxmysql** await APIs; remove callback pyramids.
Q: How do I convert qb‑target to ox_target?
**A:** Replace addBoxZone/Entity API calls one‑for‑one; event payloads remain similar. Keep target names stable.
{ “@context”: “https://schema.org”, “@type”: “FAQPage”, “mainEntity”: [ { “@type”: “Question”, “name”: “Can I port QBCore scripts to QBOX without a full rewrite?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “With adapters and ox_lib callbacks, most scripts need only call remapping.”} }, { “@type”: “Question”, “name”: “What do I do with black/dirty money?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Standardize in your adapter: ESX → black_money account; QB → item or extra account; QBOX → item or alt wallet (e.g., crypto).”} }, { “@type”: “Question”, “name”: “Why do callbacks hang after moving to QBOX?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “ESX/QB callbacks won’t fire on QBOX. Use lib.callback.register/await.”} }, { “@type”: “Question”, “name”: “Best way to migrate mysql-async?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Replace with oxmysql await APIs; remove callback pyramids.”} }, { “@type”: “Question”, “name”: “How do I convert qb-target to ox_target?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Replace addBoxZone/Entity API calls one-for-one; event payloads remain similar. Keep target names stable.”} } ] }
## 15) Visuals (Suggestions)
- Flowchart (Discover → Map → Adapt → Migrate DB → Test → Release)
- Adapters Diagram: Script → Bridge → Framework (ESX/QB/QBOX)
- Rosetta Table: included above (export as CSV for downloads)
[Discover] → [Map APIs] → [Write Adapter] → [Migrate DB] → [Harden] → [QA] → [Release]
## 16) Legal & Ethical
- Respect licenses (MIT/GPL/NC). Do not bypass escrow or obfuscation.
- Convert only scripts you own or have permission to adapt.
## 17) Downloadables & Quick Reference
You can also use this converter (not tested though):
[https://github.com/sledgehamm3r/ESX-QBCore-Converter](https://github.com/sledgehamm3r/ESX-QBCore-Converter)
## 28) Next Steps
- Drop the bridge.lua into your resource and start converting your highest‑value scripts first.
- Standardize on ox_lib + ox_inventory + ox_target + oxmysql to stay framework‑agnostic.
- Use the checklists during review and release.
Also read [SQL & Identifiers Migration: steam/license → citizenid and Accounts → Money (ESX → QBCore/QBOX)](https://vertexmods.com/blog/sql-identifiers-migration)


