{"id":192928,"date":"2025-08-15T16:20:47","date_gmt":"2025-08-15T14:20:47","guid":{"rendered":"https:\/\/fivemx.com\/?p=192928"},"modified":"2026-06-22T20:39:15","modified_gmt":"2026-06-22T18:39:15","slug":"convertendo-scripts-fivem","status":"publish","type":"post","link":"https:\/\/fivemx.com\/pt\/converting-fivem-scripts\/","title":{"rendered":"Convertendo scripts FiveM \u2013 ESX, QBCore, QBOX (Frame\u2026"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">This is a code\u2011first, no\u2011fluff easy converting FiveM scripts guide that shows you exactly how to <strong>Convert FiveM Scripts<\/strong> between ESX, QBCore, QBOX (qbx_core), and framework\u2011agnostic (standalone) setups. You\u2019ll get tri\u2011way API mappings, adapter code, SQL migration steps (mysql\u2011async \u2192 oxmysql), QBOX specifics, testing checklists, and production hardening tips.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">TL;DR \u2014 Migration in 10 Steps<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Snapshot &amp; staging:<\/strong> <a href=\"https:\/\/fivemx.com\/how-to-backup-your-fivem-server\/\" data-type=\"post\" data-id=\"162229\">Back up DB\/files<\/a>. Spin up a local test server with the target framework.<\/li>\n\n\n\n<li><strong>Identify dependencies:<\/strong> inventory, target, menu\/UI, callbacks, DB layer, <a class=\"wpil_keyword_link\" href=\"https:\/\/fivemx.com\/brand\/core\/\" title=\"Core\" data-wpil-keyword-link=\"linked\" data-wpil-monitor-id=\"1638\">core<\/a> access.<\/li>\n\n\n\n<li><strong>Pin versions &amp; order:<\/strong> oxmysql \u2192 ox_lib \u2192 framework (es_extended \/ qb-core \/ qbx_core) \u2192 your resources.<\/li>\n\n\n\n<li><strong>Read the code path:<\/strong> Find all player, money, job\/duty, inventory, callback\/event, and DB calls.<\/li>\n\n\n\n<li><strong>Map APIs:<\/strong> Use the Rosetta tables below to map ESX \u2194 QBCore \u2194 QBOX.<\/li>\n\n\n\n<li><strong>Bridge it:<\/strong> Add a <strong>bridge.lua<\/strong> adapter that auto\u2011detects the framework and normalizes calls.<\/li>\n\n\n\n<li><strong>Convert callbacks:<\/strong> ESX\/QB callbacks \u2192 QBOX\/ox_lib <code>lib.callback.*<\/code> when needed.<\/li>\n\n\n\n<li><strong>Migrate DB:<\/strong> Move identifiers, accounts, items, vehicles; switch to <strong>oxmysql<\/strong> <code>*.await<\/code> style.<\/li>\n\n\n\n<li><strong>Harden events:<\/strong> Validate <code>source<\/code>, bounds, groups\/jobs, ownership; never trust client.<\/li>\n\n\n\n<li><strong>QA &amp; release:<\/strong> Run the checklists; tag, changelog, and rollback plan.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Are you a developer that is looking for an Adapter solution?<\/strong> <a href=\"https:\/\/fivemx.com\/adapter-patterns\/\" data-type=\"post\" data-id=\"193012\">Check out our free adapter script here<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">1) Frameworks 101: ESX vs QBCore vs QBOX vs Standalone<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Architectural overview<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th><strong>Category<\/strong><\/th><th><strong>ESX<\/strong><\/th><th><strong>QBCore<\/strong><\/th><th><strong>QBOX<\/strong><\/th><\/tr><\/thead><tbody><tr><td><strong>Architecture \/ Core Access<\/strong><\/td><td><code>exports['es_extended']:getSharedObject()<\/code> \u2192 <code>ESX<\/code><\/td><td><code>exports['qb-core']:GetCoreObject()<\/code> \u2192 <code>QBCore<\/code><\/td><td>No global core object; use <code>exports.qbx_core:*<\/code> for player, groups, and notifications<\/td><\/tr><tr><td><strong>Player Object<\/strong><\/td><td><code>ESX.GetPlayerFromId(src)<\/code> \u2192 <code>xPlayer<\/code><\/td><td><code>QBCore.Functions.GetPlayer(src)<\/code> \u2192 <code>Player<\/code><\/td><td><code>exports.qbx_core:GetPlayer(src)<\/code> \u2192 <code>Player<\/code> with <code>PlayerData<\/code><\/td><\/tr><tr><td><strong>Identifiers<\/strong><\/td><td>Historically <code>identifier<\/code> (license\/steam)<\/td><td><code>citizenid<\/code> as primary key<\/td><td><code>citizenid<\/code> as primary key<\/td><\/tr><tr><td><strong>Money <a class=\"wpil_keyword_link\" href=\"https:\/\/fivemx.com\/fivem-vehicle-handling-editor\/\" title=\"FiveM Vehicle Handling Editor\" data-wpil-keyword-link=\"linked\" data-wpil-monitor-id=\"1827\">Handling<\/a><\/strong><\/td><td><code>xPlayer.addMoney()<\/code> \/ `addAccountMoney(&#8216;bank&#8217;<\/td><td>&#8216;black_money&#8217;)`<\/td><td>`Player.Functions.AddMoney(&#8216;cash&#8217;<\/td><\/tr><tr><td><strong>Jobs &amp; Duty<\/strong><\/td><td><code>xPlayer.job.name<\/code>, <code>.job.grade<\/code><\/td><td><code>Player.PlayerData.job.name<\/code>, <code>.grade.level<\/code>, <code>OnDuty<\/code><\/td><td>Job\/group helpers via <code>exports.qbx_core:*<\/code> (group checks, duty setters)<\/td><\/tr><tr><td><strong>Inventory<\/strong><\/td><td>ESX default \/ <code>ox_inventory<\/code> \/ <code>qb-inventory<\/code> (recommended: <code>ox_inventory<\/code>)<\/td><td><code>qb-inventory<\/code> \/ <code>ox_inventory<\/code><\/td><td><code>ox_inventory<\/code> recommended<\/td><\/tr><tr><td><strong>Callbacks<\/strong><\/td><td><code>ESX.RegisterServerCallback<\/code> \/ <code>ESX.TriggerServerCallback<\/code><\/td><td><code>QBCore.Functions.CreateCallback<\/code> \/ <code>TriggerCallback<\/code><\/td><td>Prefer <code>ox_lib<\/code> callbacks (<code>lib.callback.register\/await<\/code>) or QBOX exports<\/td><\/tr><tr><td><strong>Database Layer<\/strong><\/td><td>Old: <code>mysql-async<\/code> \u2192 Now: <code>oxmysql<\/code> (<code>MySQL.query.await<\/code>, <code>MySQL.update.await<\/code>)<\/td><td><code>oxmysql<\/code><\/td><td><code>oxmysql<\/code><\/td><\/tr><tr><td><strong>UI \u2013 Notifications<\/strong><\/td><td>ESX Notify<\/td><td>QB Notify<\/td><td>QBOX notify helpers or <code>lib.notify<\/code><\/td><\/tr><tr><td><strong>UI \u2013 Menus<\/strong><\/td><td><code>esx_menu_default<\/code> \/ <code>ox_lib:registerContext<\/code><\/td><td><code>qb-menu<\/code> \/ <code>ox_lib:registerContext<\/code><\/td><td><code>ox_lib:registerContext<\/code> \/ <code>lib.inputDialog<\/code><\/td><\/tr><tr><td><strong>UI \u2013 Target System<\/strong><\/td><td><code>esx_target<\/code> \/ <code>ox_target<\/code><\/td><td><code>qb-target<\/code> \/ <code>ox_target<\/code><\/td><td><code>ox_target<\/code> recommended<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Where QBOX differs<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Resource name and exports: <strong><code>qbx_core<\/code><\/strong> (no <code>GetCoreObject<\/code>).<\/li>\n\n\n\n<li>Leans on <strong>ox_lib<\/strong> (callbacks, notify) and <strong>oxmysql<\/strong> by default.<\/li>\n\n\n\n<li>Built\u2011ins for multicharacter\/queue\/groups; opinionated helpers for jobs\/duty.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img fetchpriority=\"high\" decoding=\"async\" width=\"1024\" height=\"576\" src=\"https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-1024x576.webp\" alt=\"FiveM Frameworks: QBCore vs. ESX\" class=\"wp-image-88953\" style=\"width:475px;height:auto\" srcset=\"https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-1024x576.webp 1024w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-300x169.webp 300w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-768x432.webp 768w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-18x10.webp 18w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-110x62.webp 110w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-60x34.webp 60w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks-800x450.webp 800w, https:\/\/fivemx.com\/wp-content\/uploads\/2023\/10\/fivem-frameworks.webp 1366w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">2) Preflight: Environment &amp; Tooling<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Server<\/strong>: current <a href=\"https:\/\/fivemx.com\/troubleshooting-fxserver-is-not-responding-how-to-fix\/\" data-type=\"post\" data-id=\"189713\">FXServer<\/a> artifacts, Lua 5.4, CFX <code>ensure<\/code> order<\/li>\n\n\n\n<li><strong>Order<\/strong>: <code>ensure oxmysql<\/code> \u2192 <code>ensure ox_lib<\/code> \u2192 <code>ensure framework<\/code> \u2192 <code>ensure yourResource<\/code><\/li>\n\n\n\n<li><strong>Editor<\/strong>: VS Code + <strong>Lua LS<\/strong>, <strong>stylua<\/strong>; optional <strong>ripgrep<\/strong> for quick pattern scans<\/li>\n\n\n\n<li><strong>Test setup<\/strong>: clean database snapshot; verbose logging; small seeded data set<\/li>\n\n\n\n<li><strong>Search patterns<\/strong> you\u2019ll use:\n<ul class=\"wp-block-list\">\n<li><code>ESX.<\/code> <code>xPlayer.<\/code> <code>QBCore.<\/code> <code>PlayerData<\/code> <code>qbx_core<\/code> <code>exports.ox_inventory<\/code> <code>mysql%-async<\/code> <code>MySQL.<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">3) Rosetta Stone: ESX \u2194 QBCore \u2194 QBOX (Tri\u2011way Mapping)<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Copy\/paste friendly reference. Use it to replace calls or wire your adapter.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Core &amp; Player<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Get core<\/td><td><code>exports['es_extended']:getSharedObject()<\/code><\/td><td><code>exports['qb-core']:GetCoreObject()<\/code><\/td><td><em>(n\/a \u2014 use exports)<\/em><\/td><\/tr><tr><td>Get player (src)<\/td><td><code>ESX.GetPlayerFromId(src)<\/code><\/td><td><code>QBCore.Functions.GetPlayer(src)<\/code><\/td><td><code>exports.qbx_core:GetPlayer(src)<\/code><\/td><\/tr><tr><td>Get by citizenid<\/td><td><em>(custom query)<\/em><\/td><td><code>QBCore.Functions.GetPlayerByCitizenId(id)<\/code><\/td><td><code>exports.qbx_core:GetPlayerByCitizenId(id)<\/code> <em>(if present)<\/em><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Identifiers<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Primary<\/td><td><code>identifier<\/code> (license\/steam)<\/td><td><code>citizenid<\/code><\/td><td><code>citizenid<\/code><\/td><\/tr><tr><td>Crosswalk<\/td><td>table <code>id_map(identifier, citizenid)<\/code><\/td><td>same<\/td><td>same<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Money<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Action<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Add cash<\/td><td><code>xPlayer.addMoney(a)<\/code><\/td><td><code>Player.Functions.AddMoney('cash', a)<\/code><\/td><td><code>Player.PlayerData.money.cash += a<\/code> <em>(via adapter + save)<\/em><\/td><\/tr><tr><td>Add bank<\/td><td><code>xPlayer.addAccountMoney('bank', a)<\/code><\/td><td><code>Player.Functions.AddMoney('bank', a)<\/code><\/td><td><code>Player.PlayerData.money.bank += a<\/code> <em>(adapter + save)<\/em><\/td><\/tr><tr><td>Add dirty<\/td><td><code>xPlayer.addAccountMoney('black_money', a)<\/code><\/td><td>item or extra account (server\u2011defined)<\/td><td>map to item\/alt wallet (e.g., <code>crypto<\/code>) <em>(adapter choice)<\/em><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Jobs, Duty, Groups<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Read job<\/td><td><code>xPlayer.job.name<\/code>, <code>.grade<\/code><\/td><td><code>Player.PlayerData.job.name<\/code>, <code>.grade.level<\/code><\/td><td><code>Player.PlayerData.job.*<\/code> + group helpers<\/td><\/tr><tr><td>On duty<\/td><td><em>(varies)<\/em><\/td><td><code>Player.PlayerData.job.onduty<\/code><\/td><td><code>SetJobDuty<\/code>\/group helpers via exports<\/td><\/tr><tr><td>Group check<\/td><td><em>(add-on)<\/em><\/td><td><em>(add-on)<\/em><\/td><td><code>exports.qbx_core:HasGroup(src, group)<\/code> <em>(example)<\/em><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Inventory<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Action<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Add item<\/td><td><code>xPlayer.addInventoryItem(n, c)<\/code><\/td><td><code>Player.Functions.AddItem(n, c, ...)<\/code><\/td><td>prefer <code>ox_inventory<\/code> export via adapter<\/td><\/tr><tr><td>ox_inventory<\/td><td><code>exports.ox_inventory:*<\/code><\/td><td><code>exports.ox_inventory:*<\/code><\/td><td><code>exports.ox_inventory:*<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">Callbacks<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Server register<\/td><td><code>ESX.RegisterServerCallback<\/code><\/td><td><code>QBCore.Functions.CreateCallback<\/code><\/td><td><code>lib.callback.register<\/code> (ox_lib)<\/td><\/tr><tr><td>Client trigger<\/td><td><code>ESX.TriggerServerCallback<\/code><\/td><td><code>QBCore.Functions.TriggerCallback<\/code><\/td><td><code>lib.callback.await<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">DB Layer<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX\/QB (legacy)<\/th><th>oxmysql (target)<\/th><\/tr><\/thead><tbody><tr><td>Fetch<\/td><td><code>MySQL.Async.fetchAll(sql, params, cb)<\/code><\/td><td><code>local rows = MySQL.query.await(sql, {p1, ...})<\/code><\/td><\/tr><tr><td>Update<\/td><td><code>MySQL.Async.execute(sql, params, cb)<\/code><\/td><td><code>local aff = MySQL.update.await(sql, {p1, ...})<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">UI \/ Notify \/ Target<\/h3>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Concept<\/th><th>ESX<\/th><th>QBCore<\/th><th>QBOX<\/th><\/tr><\/thead><tbody><tr><td>Notify<\/td><td><code>ESX.ShowNotification(msg)<\/code><\/td><td><code>QBCore.Functions.Notify(msg, type)<\/code><\/td><td><code>exports.qbx_core:Notify(...)<\/code> or <code>lib.notify({...})<\/code><\/td><\/tr><tr><td>Menu<\/td><td><em>(varies)<\/em><\/td><td><code>qb-menu<\/code><\/td><td><code>ox_lib:registerContext<\/code> \/ inputs<\/td><\/tr><tr><td>Target<\/td><td><em>(add-on)<\/em><\/td><td><code>qb-target<\/code><\/td><td><code>ox_target<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Tip:<\/strong> favor <strong>ox_lib<\/strong>, <strong>ox_inventory<\/strong>, <strong>ox_target<\/strong> to stay neutral across frameworks.<\/p>\n<\/blockquote>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">4) Tutorial A \u2014 ESX \u2192 QBCore (Hands\u2011on)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Goal:<\/strong> Convert a simple shop resource from ESX to QBCore.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Step 1 \u2014 Dependencies<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Ensure <code>oxmysql<\/code>, <code>ox_lib<\/code>, <code>qb-core<\/code> are started.<\/li>\n\n\n\n<li>If the script uses ESX inventory, migrate to <strong>ox_inventory<\/strong> (recommended) or map to QB inventory.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Step 2 \u2014 Detect framework &amp; add a bridge<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Create <code>bridge.lua<\/code> to normalize players\/money\/inventory:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- bridge.lua (ESX\/QB\/QBOX adapter)\nlocal M = {}\nlocal fw\n\nCreateThread(function()\n  if GetResourceState('qbx_core') == 'started' then\n    fw = 'qbx'\n  elseif GetResourceState('qb-core') == 'started' then\n    fw = 'qb'\n  elseif GetResourceState('es_extended') == 'started' then\n    fw = 'esx'\n  else\n    fw = 'none'\n  end\nend)\n\nfunction M.getFramework()\n  return fw\nend\n\nfunction M.getPlayer(src)\n  if fw == 'qbx' then\n    return exports.qbx_core:GetPlayer(src)\n  elseif fw == 'qb' then\n    return exports&#091;'qb-core']:GetCoreObject().Functions.GetPlayer(src)\n  elseif fw == 'esx' then\n    return exports&#091;'es_extended']:getSharedObject().GetPlayerFromId(src)\n  end\nend\n\nlocal function saveQbox(src)\n  if fw == 'qbx' and exports.qbx_core and exports.qbx_core.Save then\n    exports.qbx_core:Save(src)\n  end\nend\n\nfunction M.addMoney(src, account, amount)\n  local p = M.getPlayer(src)\n  if not p or type(amount) ~= 'number' then return false end\n  if fw == 'qb' then\n    return p.Functions.AddMoney(account, amount)\n  elseif fw == 'esx' then\n    if account == 'cash' then\n      return p.addMoney(amount)\n    elseif account == 'bank' then\n      return p.addAccountMoney('bank', amount)\n    elseif account == 'black' or account == 'black_money' then\n      return p.addAccountMoney('black_money', amount)\n    end\n  elseif fw == 'qbx' then\n    local money = p.PlayerData and p.PlayerData.money or {}\n    account = account == 'black' and 'crypto' or account -- example mapping\n    money&#091;account] = (money&#091;account] or 0) + amount\n    saveQbox(src)\n    return true\n  end\n  return false\nend\n\nfunction M.removeMoney(src, account, amount)\n  return M.addMoney(src, account, -math.abs(amount))\nend\n\nfunction M.addItem(src, name, count, meta)\n  if GetResourceState('ox_inventory') == 'started' then\n    return exports.ox_inventory:AddItem(src, name, count, meta)\n  elseif fw == 'qb' then\n    return exports&#091;'qb-inventory']:AddItem(src, name, count, false, meta)\n  elseif fw == 'esx' then\n    local p = M.getPlayer(src)\n    return p and p.addInventoryItem(name, count)\n  elseif fw == 'qbx' then\n    return exports.ox_inventory:AddItem(src, name, count, meta)\n  end\nend\n\nfunction M.notify(src, msg, ntype)\n  if fw == 'qb' then\n    TriggerClientEvent('QBCore:Notify', src, msg, ntype or 'primary')\n  elseif fw == 'esx' then\n    TriggerClientEvent('esx:showNotification', src, msg)\n  elseif fw == 'qbx' then\n    if exports.qbx_core and exports.qbx_core.Notify then\n      exports.qbx_core:Notify(src, msg, ntype or 'info')\n    else\n      lib.notify(src, { title = 'Notice', description = msg })\n    end\n  else\n    print(('notify(%s): %s'):format(src, msg))\n  end\nend\n\nreturn M\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 3 \u2014 Convert callbacks<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ESX \u2192 QB: replace <code>ESX.RegisterServerCallback<\/code> with <code>QBCore.Functions.CreateCallback<\/code>.<\/li>\n\n\n\n<li>Client uses <code>QBCore.Functions.TriggerCallback<\/code>.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>-- server.lua (callback)\nlocal bridge = require 'bridge'\n\nlocal function getPrice(item)\n  return 100\nend\n\nif GetResourceState('qb-core') == 'started' then\n  local QBCore = exports&#091;'qb-core']:GetCoreObject()\n  QBCore.Functions.CreateCallback('shop:getPrice', function(source, cb, item)\n    cb(getPrice(item))\n  end)\nelse\n  -- fallback for QBOX\/ESX via ox_lib\n  lib.callback.register('shop:getPrice', function(source, item)\n    return getPrice(item)\n  end)\nend\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>-- client.lua (callback)\nif GetResourceState('qb-core') == 'started' then\n  local QBCore = exports&#091;'qb-core']:GetCoreObject()\n  QBCore.Functions.TriggerCallback('shop:getPrice', function(price)\n    print('price', price)\n  end, 'bread')\nelse\n  local price = lib.callback.await('shop:getPrice', false, 'bread')\n  print('price', price)\nend\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 4 \u2014 Money &amp; inventory paths<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Replace <code>xPlayer.addAccountMoney<\/code> with <code>Player.Functions.AddMoney<\/code> (for QB) or adapter.<\/li>\n\n\n\n<li>Standardize all inventory changes through the adapter.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Step 5 \u2014 Events (server\u2011side security)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>RegisterNetEvent('shop:buy', function(item, amount)\n  local src = source\n  if type(item) ~= 'string' or type(amount) ~= 'number' then return end\n  if amount &lt; 1 or amount &gt; 10 then return end\n\n  local p = bridge.getPlayer(src)\n  if not p then return end\n\n  local price = 100 * amount\n  if not bridge.removeMoney(src, 'cash', price) then return end\n  bridge.addItem(src, item, amount)\n  bridge.notify(src, ('Bought %dx %s'):format(amount, item), 'success')\nend)\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 6 \u2014 DB migration (identifiers, accounts)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create a crosswalk <code>id_map(identifier, citizenid)<\/code>.<\/li>\n\n\n\n<li>Populate <code>citizenid<\/code> for QB from ESX <code>users<\/code> table via rule of choice (existing column or generated).<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Example: create crosswalk and backfill (customize to your schema)\nCREATE TABLE IF NOT EXISTS id_map (\n  identifier VARCHAR(64) PRIMARY KEY,\n  citizenid  VARCHAR(64) NOT NULL UNIQUE\n);\n\n-- Suppose you generated new citizenids and stored them earlier\nINSERT IGNORE INTO id_map(identifier, citizenid)\nSELECT u.identifier, u.citizenid FROM users u WHERE u.citizenid IS NOT NULL;\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>mysql\u2011async \u2192 oxmysql<\/strong><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>-- before (mysql-async)\nMySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {&#091;'@id'] = identifier}, function(rows) ... end)\n\n-- after (oxmysql)\nlocal rows = MySQL.query.await('SELECT * FROM users WHERE identifier = ?', { identifier })\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Step 7 \u2014 QA<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Spawn items, buy\/sell, ensure balances change correctly.<\/li>\n\n\n\n<li>Verify callbacks return under load.<\/li>\n\n\n\n<li>Check that inventory metadata is preserved.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">5) Tutorial B \u2014 QBCore \u2192 ESX (Hands\u2011on)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Key caveats:<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>ESX uses <strong>accounts<\/strong> for <code>bank<\/code>\/<code>black_money<\/code>. Port QB\u2019s dirty\u2011money <strong>item<\/strong> (if used) to ESX account or keep it as an item.<\/li>\n\n\n\n<li>Job schema differs (grade vs level). Map carefully.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Example: Money mapping via adapter<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- in bridge.lua, when fw == 'esx'\nfunction M.qbToEsxMoney(account)\n  if account == 'cash' then return 'cash' end\n  if account == 'bank' then return 'bank' end\n  if account == 'black' or account == 'black_money' then return 'black_money' end\n  return account\nend\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Example: Callback conversion (server)<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>if GetResourceState('es_extended') == 'started' then\n  local ESX = exports&#091;'es_extended']:getSharedObject()\n  ESX.RegisterServerCallback('garage:getVehicles', function(source, cb)\n    local src = source\n    local p = bridge.getPlayer(src)\n    local rows = MySQL.query.await('SELECT * FROM owned_vehicles WHERE owner = ?', { p.identifier })\n    cb(rows)\n  end)\nend\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Testing checklist<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Job set\/promotions persist.<\/li>\n\n\n\n<li>Bank\/dirty balances mutate as expected.<\/li>\n\n\n\n<li>Vehicle ownership and plate formats validated.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">6) Tutorial C \u2014 QBCore \u2194 QBOX (Hands\u2011on)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>QBCore \u2192 QBOX<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Replace <code>QBCore.Functions.*<\/code> with <code>exports.qbx_core:*<\/code> or <strong>ox_lib<\/strong> callbacks.<\/li>\n\n\n\n<li>Player: <code>QBCore.Functions.GetPlayer(src)<\/code> \u2192 <code>exports.qbx_core:GetPlayer(src)<\/code>.<\/li>\n\n\n\n<li>Notify: <code>QBCore.Functions.Notify<\/code> \u2192 <code>exports.qbx_core:Notify<\/code> or <code>lib.notify<\/code>.<\/li>\n\n\n\n<li>Duty\/groups: use QBOX exports (<code>SetJobDuty<\/code>, <code>HasGroup<\/code>, etc.).<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>QBOX \u2192 QBCore<\/strong><\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Replace <code>exports.qbx_core:*<\/code> with <code>QBCore.Functions.*<\/code> equivalents or your adapter.<\/li>\n\n\n\n<li>Reintroduce QB callbacks and menu\/target equivalents as needed.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Before\/After snippets<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Before (QB notify)\nQBCore.Functions.Notify('Hello', 'success')\n\n-- After (QBOX)\nexports.qbx_core:Notify(source, 'Hello', 'success')\n-- or\nlib.notify(source, { title = 'Hello', description = 'Welcome', type = 'success' })\n<\/code><\/pre>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Before (QB get player)\nlocal Player = QBCore.Functions.GetPlayer(src)\n\n-- After (QBOX)\nlocal Player = exports.qbx_core:GetPlayer(src)\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">7) Tutorial D \u2014 Framework \u2192 Standalone with Adapters<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Write your scripts <strong>once<\/strong> and run them anywhere:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create a <strong>bridge<\/strong> exposing a stable API: <code>getPlayer<\/code>, <code>getIdentifier<\/code>, <code>add\/removeMoney<\/code>, <code>add\/removeItem<\/code>, <code>notify<\/code>, <code>hasGroup<\/code>, <code>onDuty<\/code>, <code>callbacks<\/code> wrapper.<\/li>\n\n\n\n<li>Detect framework at runtime (<code>qbx_core<\/code> \u2192 <code>qb-core<\/code> \u2192 <code>es_extended<\/code> \u2192 <code>none<\/code>).<\/li>\n\n\n\n<li>Use <strong>ox_lib<\/strong> for callbacks and notifications when no direct framework helper exists.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Minimal standalone fallback<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- when fw == 'none'\nfunction M.getPlayer(src)\n  -- implement a minimal table or reject actions gracefully\n  return { id = src }\nend\n\nfunction M.notify(src, msg)\n  print(('notify(%s): %s'):format(src, msg))\nend\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">8) Performance &amp; Security (Production Hardening)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Server\u2011side validation (never trust client)<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Validate <strong>types<\/strong> and <strong>bounds<\/strong> on every event.<\/li>\n\n\n\n<li>Check <strong>ownership<\/strong>, <strong>cooldowns<\/strong>, <strong>distance<\/strong> (if relevant), <strong>job\/group<\/strong>.<\/li>\n\n\n\n<li>Ensure money can\u2019t go negative; clamp amounts.<\/li>\n\n\n\n<li>Compare <strong>price<\/strong> server\u2011side; don\u2019t accept client\u2011provided totals.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Event structure<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Use one <strong>server event<\/strong> per action; do not expose raw inventory\/money functions to clients.<\/li>\n\n\n\n<li>Prefer <strong>callbacks<\/strong> for request\/response flows.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">DB &amp; performance<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Switch to <strong>oxmysql<\/strong> await APIs; batch writes; avoid per\u2011tick queries.<\/li>\n\n\n\n<li>Index frequently queried columns (<code>citizenid<\/code>, <code>identifier<\/code>, <code>plate<\/code>).<\/li>\n\n\n\n<li>Cache config\/price lists in memory; export them to clients once, then diff on change.<\/li>\n\n\n\n<li>Use <code>GlobalState<\/code> sparingly; avoid hot\u2011loop updates.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">9) Common Pitfalls &amp; Debugging Playbook<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Assuming <code>GetCoreObject<\/code> exists on QBOX<\/strong> \u2192 it doesn\u2019t; use <code>exports.qbx_core:*<\/code>.<\/li>\n\n\n\n<li><strong>Callbacks never return after migrating to QBOX<\/strong> \u2192 ESX\/QB callbacks aren\u2019t registered; switch to <code>lib.callback.register\/await<\/code>.<\/li>\n\n\n\n<li><strong>Dirty money semantics<\/strong> differ \u2192 decide item vs account vs alt wallet and standardize in your adapter.<\/li>\n\n\n\n<li><strong>Mixed inventory assumptions<\/strong> \u2192 normalize on <code>ox_inventory<\/code>.<\/li>\n\n\n\n<li><strong>Start order issues<\/strong> \u2192 <code>oxmysql<\/code> \u2192 <code>ox_lib<\/code> \u2192 framework \u2192 your resources.<\/li>\n\n\n\n<li><strong>Vehicle metadata drift<\/strong> \u2192 ensure JSON columns and plate formats match target framework.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">10) fxmanifest.lua Best Practices<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>fx_version 'cerulean'\ngame 'gta5'\n\nlua54 'yes'\n\nshared_scripts {\n  '@ox_lib\/init.lua',\n  'bridge.lua',\n  'config.lua'\n}\nclient_scripts {\n  'client\/*.lua'\n}\nserver_scripts {\n  '@oxmysql\/lib\/MySQL.lua',\n  'server\/*.lua'\n}\n\nescrow_ignore {\n  'bridge.lua', 'config.lua'\n}\n\n-- dependencies (pick what you use): ox_lib, oxmysql, ox_inventory, qb-core OR qbx_core OR es_extended\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Declare <strong>Lua 5.4<\/strong> (<code>lua54 'yes'<\/code>).<\/li>\n\n\n\n<li>Keep <code>escrow_ignore<\/code> minimal; never attempt escrow bypass.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">11) Data Migration: SQL Snippets (Examples)<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\"><strong>Adjust table\/column names to your schema.<\/strong> Always run on a copy first.<\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">Identifiers: ESX \u2192 QB\/QBOX<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Add citizenid to ESX users if missing\nALTER TABLE users ADD COLUMN IF NOT EXISTS citizenid VARCHAR(64);\n\n-- Populate using a deterministic generator or an existing column\nUPDATE users SET citizenid = LOWER(SUBSTRING(MD5(CONCAT(identifier,'-QB')),1,10))\nWHERE citizenid IS NULL;\n\n-- Create crosswalk\nCREATE TABLE IF NOT EXISTS id_map (\n  identifier VARCHAR(64) PRIMARY KEY,\n  citizenid  VARCHAR(64) NOT NULL UNIQUE\n);\nINSERT IGNORE INTO id_map(identifier, citizenid)\nSELECT identifier, citizenid FROM users WHERE citizenid IS NOT NULL;\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Accounts\/Money<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Example: convert ESX bank balances to QB money table (if your QB schema stores it separately)\nINSERT INTO player_money (citizenid, account, amount)\nSELECT m.citizenid, 'bank', a.money\nFROM (\n  SELECT u.citizenid, SUM(CASE WHEN account = 'bank' THEN money ELSE 0 END) AS money\n  FROM user_accounts ua\n  JOIN users u ON u.identifier = ua.identifier\n  GROUP BY u.citizenid\n) a\nJOIN users m ON m.citizenid = a.citizenid\nON DUPLICATE KEY UPDATE amount = VALUES(amount);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Vehicles<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>-- Normalize plates to target format (example: 8 chars upper)\nUPDATE owned_vehicles SET plate = UPPER(LEFT(plate,8));\n\n-- Ensure JSON metadata columns are valid\nUPDATE owned_vehicles SET mods = JSON_MERGE_PATCH('{}', mods) WHERE JSON_VALID(mods) = 0;\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">12) Adapters You Can Reuse (Starter)<\/h2>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Drop this into <code>bridge.lua<\/code> and expand per your needs. It auto\u2011detects ESX\/QB\/QBOX and exposes a stable API.<\/p>\n<\/blockquote>\n\n\n\n<pre class=\"wp-block-code\"><code>local M = {}\nlocal fw = 'none'\n\nCreateThread(function()\n  if GetResourceState('qbx_core') == 'started' then fw = 'qbx'\n  elseif GetResourceState('qb-core') == 'started' then fw = 'qb'\n  elseif GetResourceState('es_extended') == 'started' then fw = 'esx' end\nend)\n\nfunction M.framework() return fw end\n\nfunction M.player(src)\n  if fw == 'qbx' then return exports.qbx_core:GetPlayer(src)\n  elseif fw == 'qb' then return exports&#091;'qb-core']:GetCoreObject().Functions.GetPlayer(src)\n  elseif fw == 'esx' then return exports&#091;'es_extended']:getSharedObject().GetPlayerFromId(src) end\nend\n\nfunction M.identifier(src)\n  local p = M.player(src); if not p then return nil end\n  if fw == 'qb' or fw == 'qbx' then\n    return p.PlayerData and p.PlayerData.citizenid\n  elseif fw == 'esx' then\n    return p.identifier\n  end\nend\n\nfunction M.hasGroup(src, group)\n  if fw == 'qbx' and exports.qbx_core and exports.qbx_core.HasGroup then\n    return exports.qbx_core:HasGroup(src, group)\n  end\n  return false\nend\n\nfunction M.notify(src, msg, ntype)\n  if fw == 'qb' then\n    TriggerClientEvent('QBCore:Notify', src, msg, ntype or 'primary')\n  elseif fw == 'esx' then\n    TriggerClientEvent('esx:showNotification', src, msg)\n  elseif fw == 'qbx' then\n    if exports.qbx_core and exports.qbx_core.Notify then\n      exports.qbx_core:Notify(src, msg, ntype or 'info')\n    else\n      lib.notify(src, { description = msg })\n    end\n  end\nend\n\nreturn M\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">13) Final Checklists (Copy &amp; Ship)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">A) Discovery &amp; Preflight<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Snapshot DB\/files; create a staging server<\/li>\n\n\n\n<li>Pin artifacts and resource versions; define start order<\/li>\n\n\n\n<li>Inventory\/target\/menu dependencies listed<\/li>\n\n\n\n<li>Decide identifier strategy (citizenid crosswalk)<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">B) Code Audit<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Grep for ESX\/QB\/QBOX\/ox_*\/mysql\u2011async<\/li>\n\n\n\n<li>List all money\/inventory\/job\/duty calls<\/li>\n\n\n\n<li>List callbacks and server\/client events<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">C) Mapping &amp; Design<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Choose adapter surface (player, ids, money, items, notify, callbacks)<\/li>\n\n\n\n<li>Decide dirty\u2011money strategy (account vs item vs alt wallet)<\/li>\n\n\n\n<li>Favor ox_lib\/ox_inventory\/ox_target<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">D) Data Migration<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Build citizenid crosswalk<\/li>\n\n\n\n<li>Convert accounts\/money<\/li>\n\n\n\n<li>Normalize vehicle metadata and plates<\/li>\n\n\n\n<li>Switch to oxmysql await calls<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">E) QA Testing<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Event security: types\/bounds\/ownership\/cooldowns<\/li>\n\n\n\n<li>Callbacks: return values under load<\/li>\n\n\n\n<li>Inventory metadata preserved<\/li>\n\n\n\n<li>Jobs\/duty\/group logic matches design<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">F) Release &amp; Rollback<\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tag release; changelog<\/li>\n\n\n\n<li>Keep pre\u2011migration snapshot for 7\u201314 days<\/li>\n\n\n\n<li>Monitor errors\/latency; index hot queries<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">14) FAQ (with JSON\u2011LD Schema)<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q: Can I port QBCore scripts to QBOX without a full rewrite?<\/strong><br><strong>A:<\/strong> With adapters and <strong>ox_lib<\/strong> callbacks, most scripts need only call remapping.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q: What do I do with black\/dirty money?<\/strong><br><strong>A:<\/strong> Standardize in your adapter: ESX \u2192 <code>black_money<\/code> account; QB \u2192 item or extra account; QBOX \u2192 item or alt wallet (e.g., crypto). Keep one strategy project\u2011wide.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q: Why do callbacks hang after moving to QBOX?<\/strong><br><strong>A:<\/strong> ESX\/QB callbacks won\u2019t fire on QBOX. Use <code>lib.callback.register\/await<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q: Best way to migrate mysql\u2011async?<\/strong><br><strong>A:<\/strong> Replace with <strong>oxmysql<\/strong> await APIs; remove callback pyramids.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Q: How do I convert qb\u2011target to ox_target?<\/strong><br><strong>A:<\/strong> Replace addBoxZone\/Entity API calls one\u2011for\u2011one; event payloads remain similar. Keep target names stable.<\/p>\n\n\n\n\n{\n  &#8220;@context&#8221;: &#8220;https:\/\/schema.org&#8221;,\n  &#8220;@type&#8221;: &#8220;FAQPage&#8221;,\n  &#8220;mainEntity&#8221;: [\n    {\n      &#8220;@type&#8221;: &#8220;Question&#8221;,\n      &#8220;name&#8221;: &#8220;Can I port QBCore scripts to QBOX without a full rewrite?&#8221;,\n      &#8220;acceptedAnswer&#8221;: {&#8220;@type&#8221;: &#8220;Answer&#8221;, &#8220;text&#8221;: &#8220;With adapters and ox_lib callbacks, most scripts need only call remapping.&#8221;}\n    },\n    {\n      &#8220;@type&#8221;: &#8220;Question&#8221;,\n      &#8220;name&#8221;: &#8220;What do I do with black\/dirty money?&#8221;,\n      &#8220;acceptedAnswer&#8221;: {&#8220;@type&#8221;: &#8220;Answer&#8221;, &#8220;text&#8221;: &#8220;Standardize in your adapter: ESX \u2192 black_money account; QB \u2192 item or extra account; QBOX \u2192 item or alt wallet (e.g., crypto).&#8221;}\n    },\n    {\n      &#8220;@type&#8221;: &#8220;Question&#8221;,\n      &#8220;name&#8221;: &#8220;Why do callbacks hang after moving to QBOX?&#8221;,\n      &#8220;acceptedAnswer&#8221;: {&#8220;@type&#8221;: &#8220;Answer&#8221;, &#8220;text&#8221;: &#8220;ESX\/QB callbacks won\u2019t fire on QBOX. Use lib.callback.register\/await.&#8221;}\n    },\n    {\n      &#8220;@type&#8221;: &#8220;Question&#8221;,\n      &#8220;name&#8221;: &#8220;Best way to migrate mysql-async?&#8221;,\n      &#8220;acceptedAnswer&#8221;: {&#8220;@type&#8221;: &#8220;Answer&#8221;, &#8220;text&#8221;: &#8220;Replace with oxmysql await APIs; remove callback pyramids.&#8221;}\n    },\n    {\n      &#8220;@type&#8221;: &#8220;Question&#8221;,\n      &#8220;name&#8221;: &#8220;How do I convert qb-target to ox_target?&#8221;,\n      &#8220;acceptedAnswer&#8221;: {&#8220;@type&#8221;: &#8220;Answer&#8221;, &#8220;text&#8221;: &#8220;Replace addBoxZone\/Entity API calls one-for-one; event payloads remain similar. Keep target names stable.&#8221;}\n    }\n  ]\n}\n\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">15) Visuals (Suggestions)<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Flowchart<\/strong> (Discover \u2192 Map \u2192 Adapt \u2192 Migrate DB \u2192 Test \u2192 Release)<\/li>\n\n\n\n<li><strong>Adapters Diagram:<\/strong> Script \u2192 Bridge \u2192 Framework (ESX\/QB\/QBOX)<\/li>\n\n\n\n<li><strong>Rosetta Table:<\/strong> included above (export as CSV for downloads)<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>&#091;Discover] \u2192 &#091;Map APIs] \u2192 &#091;Write Adapter] \u2192 &#091;Migrate DB] \u2192 &#091;Harden] \u2192 &#091;QA] \u2192 &#091;Release]\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">16) Legal &amp; Ethical<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Respect licenses (MIT\/GPL\/NC). <strong>Do not<\/strong> bypass escrow or obfuscation.<\/li>\n\n\n\n<li>Convert only scripts you <strong>own<\/strong> or have permission to adapt.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">17) Downloadables &amp; Quick Reference<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You can also use this converter (not tested though):<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"https:\/\/github.com\/sledgehamm3r\/ESX-QBCore-Converter\" target=\"_blank\" rel=\"noreferrer noopener\">https:\/\/github.com\/sledgehamm3r\/ESX-QBCore-Converter<\/a><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\">28) Next Steps<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/fivemx.com\/adapter-patterns\/\" data-type=\"post\" data-id=\"193012\">Drop the <strong>bridge.lua<\/strong><\/a> into your resource and start converting your highest\u2011value scripts first.<\/li>\n\n\n\n<li>Standardize on <strong>ox_lib + ox_inventory + ox_target + oxmysql<\/strong> to stay framework\u2011agnostic.<\/li>\n\n\n\n<li>Use the checklists during review and release.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<p class=\"wp-block-paragraph\">Also read <a href=\"https:\/\/fivemx.com\/sql-identifiers-migration\/\" data-type=\"link\" data-id=\"https:\/\/fivemx.com\/sql-identifiers-migration\/\">SQL &amp; Identifiers Migration: steam\/license \u2192 citizenid and Accounts \u2192 Money (ESX \u2192 QBCore\/QBOX)<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is a code\u2011first, no\u2011fluff easy converting FiveM scripts guide that shows you exactly how to Convert FiveM Scripts between ESX, QBCore, QBOX (qbx_core), and framework\u2011agnostic (standalone) setups. You\u2019ll get tri\u2011way API mappings, adapter code, SQL migration steps (mysql\u2011async \u2192 oxmysql), QBOX specifics, testing checklists, and production hardening tips. TL;DR \u2014 Migration in 10 Steps [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":192929,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2340,2882],"tags":[],"class_list":["post-192928","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-lua-scripting","category-framework-conversion"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/192928","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/comments?post=192928"}],"version-history":[{"count":1,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/192928\/revisions"}],"predecessor-version":[{"id":206923,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/192928\/revisions\/206923"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media\/192929"}],"wp:attachment":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media?parent=192928"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/categories?post=192928"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/tags?post=192928"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}