{"id":199542,"date":"2025-10-02T15:33:52","date_gmt":"2025-10-02T13:33:52","guid":{"rendered":"https:\/\/fivemx.com\/?p=199542"},"modified":"2026-06-22T20:45:49","modified_gmt":"2026-06-22T18:45:49","slug":"como-migrar-esx-qbcore","status":"publish","type":"post","link":"https:\/\/fivemx.com\/pt\/how-to-migrate-esx-qbcore\/","title":{"rendered":"Como migrar ESX \u2192 QBCore da maneira certa"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">You want a clean switch from ESX to QBCore without losing data or breaking core systems. Follow this plan. You will finish with stable identifiers, oxmysql queries, and ox_lib powered code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Goal: move your server from ESX to QBCore with minimal downtime.<\/strong><\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<div class=\"wp-block-rank-math-toc-block\" id=\"rank-math-toc\"><h2>Overview<\/h2><nav><ul><li class=\"\"><a href=\"#prerequisites\">Prerequisites<\/a><\/li><li class=\"\"><a href=\"#step-1-make-a-plan-and-a-rollback-point\">Step 1. Make a Plan and a Rollback Point<\/a><\/li><li class=\"\"><a href=\"#step-2-build-a-clean-qb-core-base\">Step 2. Build a Clean QBCore Base<\/a><\/li><li class=\"\"><a href=\"#step-3-replace-mysql-async-with-oxmysql\">Step 3. Replace mysql-async with oxmysql<\/a><ul><li class=\"\"><a href=\"#common-conversions\">Common conversions<\/a><\/li><\/ul><\/li><li class=\"\"><a href=\"#step-4-map-esx-data-structures-to-qb-core\">Step 4. Map ESX Data Structures to QBCore<\/a><\/li><li class=\"\"><a href=\"#step-5-stabilize-identifiers\">Step 5. Stabilize Identifiers<\/a><ul><li class=\"\"><a href=\"#sql-bootstrap\">SQL bootstrap<\/a><\/li><li class=\"\"><a href=\"#generate-citizenid-and-insert-players-in-lua\">Generate citizenid and insert players in Lua<\/a><\/li><li class=\"\"><a href=\"#move-owned-vehicles\">Move owned vehicles<\/a><\/li><\/ul><\/li><li class=\"\"><a href=\"#step-6-port-esx-code-to-qb-core-with-ox-lib\">Step 6. Port ESX Code to QBCore with ox_lib<\/a><ul><li class=\"\"><a href=\"#player-object\">Player object<\/a><\/li><li class=\"\"><a href=\"#jobs\">Jobs<\/a><\/li><li class=\"\"><a href=\"#callbacks-and-ui\">Callbacks and UI<\/a><\/li><li class=\"\"><a href=\"#commands\">Commands<\/a><\/li><\/ul><\/li><li class=\"\"><a href=\"#step-7-inventory-and-items\">Step 7. Inventory and Items<\/a><\/li><li class=\"\"><a href=\"#step-8-test-and-roll-out\">Step 8. Test and Roll Out<\/a><\/li><li class=\"\"><a href=\"#troubleshooting\">Troubleshooting<\/a><\/li><li class=\"\"><a href=\"#cutover-checklist\">Cutover Checklist<\/a><\/li><li class=\"\"><a href=\"#appendix-a-example-fxmanifest-for-migration-helper\">Appendix A. Example fxmanifest for migration helper<\/a><\/li><li class=\"\"><a href=\"#appendix-b-safe-json-helpers\">Appendix B. Safe JSON helpers<\/a><\/li><li class=\"\"><a href=\"#what-you-achieved\">What you achieved<\/a><\/li><li class=\"\"><a href=\"#useful-links-inside-your-site\">Useful links inside your site<\/a><\/li><li class=\"\"><a href=\"#external-references\">External references<\/a><\/li><\/ul><\/nav><\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"prerequisites\">Prerequisites<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Tools\n<ol class=\"wp-block-list\">\n<li>GIT and a separate branch for the migration.<\/li>\n\n\n\n<li>MariaDB or MySQL 8 with full backups enabled.<\/li>\n\n\n\n<li>A staging server that mirrors production.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Server artifacts\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/fivemx.com\/troubleshooting-fxserver-is-not-responding-how-to-fix\/\" data-type=\"post\" data-id=\"189713\">FXServer<\/a> updated to the same build as production.<\/li>\n\n\n\n<li><a href=\"https:\/\/fivemx.com\/qbcore-scripts\/\" data-type=\"product_cat\" data-id=\"512\">QBCore<\/a> base framework and default resources.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Libraries you will use\n<ol class=\"wp-block-list\">\n<li><code>oxmysql<\/code> for database.<\/li>\n\n\n\n<li><code>ox_lib<\/code> for callbacks, UI helpers, and utility wrappers.<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-1-make-a-plan-and-a-rollback-point\">Step 1. Make a Plan and a Rollback Point<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Freeze production changes. Stop new script installs and DB writes not required for testing.<\/li>\n\n\n\n<li>Back up your entire database dump as a named snapshot.<\/li>\n\n\n\n<li>Branch your server repository and create a dedicated <code>migrate-esx-to-qbcore<\/code> branch.<\/li>\n\n\n\n<li>Write a runbook. Include commands to start and stop the staging server, restore DB, and run health checks.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-2-build-a-clean-qb-core-base\">Step 2. Build a Clean QBCore Base<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Deploy a fresh QBCore base to staging.<\/li>\n\n\n\n<li>Keep only essentials enabled. Disable jobs, inventories, and custom scripts until after DB migration.<\/li>\n\n\n\n<li>Install and start these resources first\n<ol class=\"wp-block-list\">\n<li><code>qb-core<\/code><\/li>\n\n\n\n<li><code>qb-vehicles<\/code> or your preferred replacements<\/li>\n\n\n\n<li><code>oxmysql<\/code><\/li>\n\n\n\n<li><code>ox_lib<\/code><\/li>\n<\/ol>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-3-replace-mysql-async-with-oxmysql\">Step 3. Replace mysql-async with oxmysql<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If any remaining ESX scripts still use <code>MySQL.Async<\/code>, convert the calls to oxmysql. Use simple find and replace with verification.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"common-conversions\">Common conversions<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX mysql-async\nMySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {['@id'] = identifier}, function(rows)\n  -- ...\nend)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- QBCore oxmysql\nlocal rows = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', { citizenid })\n-- rows is a Lua table; handle nil and length checks directly\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX scalar example\nMySQL.Async.fetchScalar('SELECT COUNT(1) FROM owned_vehicles', {}, function(count)\n  -- ...\nend)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- oxmysql scalar\nlocal count = MySQL.scalar.await('SELECT COUNT(1) FROM player_vehicles')\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX insert\nMySQL.Async.execute('INSERT INTO addon_account VALUES (@owner, @name, @money)', {\n  ['@owner'] = identifier, ['@name'] = name, ['@money'] = money\n})\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- oxmysql insert\nMySQL.prepare.await('INSERT INTO player_accounts (citizenid, name, amount) VALUES (?, ?, ?)', { citizenid, name, amount })\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Notes<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Prefer <code>query.await<\/code>, <code>scalar.await<\/code>, and <code>prepare.await<\/code> for clean flow.<\/li>\n\n\n\n<li>Use prepared statements for write operations.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-4-map-esx-data-structures-to-qb-core\">Step 4. Map ESX Data Structures to QBCore<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">You will move player identities and owned entities. Use this reference to map tables.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>ESX table<\/th><th>Key column<\/th><th>QBCore table<\/th><th>Key column<\/th><th>Notes<\/th><\/tr><\/thead><tbody><tr><td><code>users<\/code><\/td><td><code>identifier<\/code><\/td><td><code>players<\/code><\/td><td><code>citizenid<\/code><\/td><td>Convert identifiers and create <code>citizenid<\/code> for each row<\/td><\/tr><tr><td><code>owned_vehicles<\/code><\/td><td><code>owner<\/code><\/td><td><code>player_vehicles<\/code><\/td><td><code>citizenid<\/code><\/td><td>Convert plate casing and JSON payloads<\/td><\/tr><tr><td><code>datastore_data<\/code><\/td><td><code>owner<\/code><\/td><td><code>player_metadata<\/code><\/td><td><code>citizenid<\/code><\/td><td>If you store JSON, merge carefully<\/td><\/tr><tr><td><code>addon_account_data<\/code><\/td><td><code>owner<\/code><\/td><td><code>player_accounts<\/code><\/td><td><code>citizenid<\/code><\/td><td>Map account names to QBCore banking or cash<\/td><\/tr><tr><td><code>addon_inventory_items<\/code><\/td><td><code>owner<\/code><\/td><td><code>player_inventories<\/code><\/td><td><code>citizenid<\/code><\/td><td>If you move to <code>ox_inventory<\/code>, migrate separately<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You can keep custom tables. Adjust only foreign keys that reference ESX identifiers.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-5-stabilize-identifiers\">Step 5. Stabilize Identifiers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">ESX often stores a CFX identifier like <code>license:xxxx<\/code> or historical <code>steam:xxxx<\/code>. QBCore uses <code>citizenid<\/code> as the stable player key and keeps the runtime identifiers for authentication only.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You will<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Create a <code>citizenid<\/code> for every player.<\/li>\n\n\n\n<li>Link legacy identifiers to the new record.<\/li>\n\n\n\n<li>Keep a lookup table for support and audits.<\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"sql-bootstrap\">SQL bootstrap<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run this on a copy of your ESX database to prepare QBCore tables.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- 1) Create players table if missing. Adjust to your QBCore schema.\nCREATE TABLE IF NOT EXISTS players (\n  citizenid VARCHAR(11) PRIMARY KEY,\n  license VARCHAR(64) UNIQUE,\n  identifiers JSON NOT NULL,\n  name VARCHAR(64),\n  charinfo JSON NOT NULL,\n  metadata JSON NOT NULL,\n  money JSON NOT NULL,\n  job JSON NOT NULL,\n  position VARCHAR(128) DEFAULT NULL,\n  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n\n-- 2) Helper function equivalent in SQL using a deterministic generator would be complex.\n-- Instead, stage the mapping in a separate table and generate citizenid in Lua.\nCREATE TABLE IF NOT EXISTS legacy_identifier_map (\n  license VARCHAR(64) PRIMARY KEY,\n  steam VARCHAR(64) NULL,\n  fivem VARCHAR(64) NULL,\n  discord VARCHAR(64) NULL,\n  xbl VARCHAR(64) NULL,\n  liveid VARCHAR(64) NULL,\n  citizenid VARCHAR(11) UNIQUE\n);\n\n-- 3) Seed the mapping from ESX users\nINSERT INTO legacy_identifier_map (license)\nSELECT DISTINCT REPLACE(identifier, 'identifier:', '')\nFROM users\nWHERE identifier LIKE 'license:%' OR identifier LIKE 'steam:%';\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"generate-citizenid-and-insert-players-in-lua\">Generate citizenid and insert players in Lua<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Run once on staging. Back up first.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- server\/migrate_identifiers.lua\nlocal QBCore = exports['qb-core']:GetCoreObject()\n\nlocal function generateCitizenId()\n  local charset = {}\n  for c = 65, 90 do table.insert(charset, string.char(c)) end\n  for n = 48, 57 do table.insert(charset, string.char(n)) end\n  math.randomseed(GetGameTimer())\n  local id = {}\n  for i = 1, 11 do id[i] = charset[math.random(1, #charset)] end\n  return table.concat(id)\nend\n\nlocal rows = MySQL.query.await('SELECT license FROM legacy_identifier_map WHERE citizenid IS NULL')\nfor _, r in ipairs(rows) do\n  local citizenid = generateCitizenId()\n  MySQL.prepare.await('UPDATE legacy_identifier_map SET citizenid = ? WHERE license = ?', { citizenid, r.license })\nend\n\n-- Build players from ESX users\nlocal users = MySQL.query.await([[SELECT u.identifier, u.firstname, u.lastname, u.dateofbirth, u.sex, u.height\n                                  FROM users u]])\nfor _, u in ipairs(users) do\n  local license = u.identifier\n  local map = MySQL.single.await('SELECT citizenid FROM legacy_identifier_map WHERE license = ?', { license })\n  if map and map.citizenid then\n    local name = string.format('%s %s', u.firstname or 'John', u.lastname or 'Doe')\n    local charinfo = json.encode({ firstname = u.firstname, lastname = u.lastname, birthdate = u.dateofbirth, gender = u.sex, height = u.height })\n    local metadata = json.encode({ hunger = 100, thirst = 100 })\n    local money = json.encode({ cash = 0, bank = 0, crypto = 0 })\n    local job = json.encode({ name = 'unemployed', label = 'Unemployed', grade = { name = '0', level = 0 }})\n\n    MySQL.prepare.await('INSERT IGNORE INTO players (citizenid, license, identifiers, name, charinfo, metadata, money, job) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {\n      map.citizenid,\n      license,\n      json.encode({ license = license }),\n      name,\n      charinfo,\n      metadata,\n      money,\n      job\n    })\n  end\nend\nprint('Identifier migration finished')\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"move-owned-vehicles\">Move owned vehicles<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">INSERT IGNORE INTO player_vehicles (citizenid, plate, vehicle, garage, state)\nSELECT m.citizenid,\n       UPPER(JSON_UNQUOTE(JSON_EXTRACT(v.vehicle, '$.plate'))),\n       v.vehicle,\n       'legion',\n       1\nFROM owned_vehicles v\nJOIN legacy_identifier_map m ON m.license = v.owner;\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Validate random samples in game. Verify plate formats and garages.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-6-port-esx-code-to-qb-core-with-ox-lib\">Step 6. Port ESX Code to QBCore with ox_lib<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Replace the ESX runtime API with QBCore equivalents. Use ox_lib for callbacks and notifications.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"player-object\">Player object<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX\nlocal xPlayer = ESX.GetPlayerFromId(src)\nxPlayer.addMoney(100)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- QBCore\nlocal Player = QBCore.Functions.GetPlayer(src)\nPlayer.Functions.AddMoney('cash', 100)\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"jobs\">Jobs<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX job check\nif xPlayer.getJob().name == 'police' then\n  -- ...\nend\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- QBCore job check\nlocal job = Player.PlayerData.job\nif job and job.name == 'police' then\n  -- ...\nend\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"callbacks-and-ui\">Callbacks and UI<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX server callback\nESX.RegisterServerCallback('resource:getData', function(source, cb)\n  cb({ ok = true })\nend)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ox_lib callback\nlib.callback.register('resource:getData', function(source)\n  return { ok = true }\nend)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- Notification\nlib.notify(source, { title = 'Job', description = 'Promotion granted', type = 'success' })\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"commands\">Commands<\/h3>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- ESX\nRegisterCommand('pay', function(src, args)\n  local amount = tonumber(args[1]) or 0\n  xPlayer.removeMoney(amount)\nend)\n<\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">-- QBCore with permissions\nQBCore.Commands.Add('pay', 'Pay cash', {{name = 'amount', help = 'Amount'}}, false, function(src, args)\n  local amount = tonumber(args[1]) or 0\n  local Player = QBCore.Functions.GetPlayer(src)\n  Player.Functions.RemoveMoney('cash', amount)\nend)\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-7-inventory-and-items\">Step 7. Inventory and Items<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">If you move from <code>es_extended<\/code> inventories to <code>qb-inventory<\/code> or <code>ox_inventory<\/code>, treat this as a separate sub\u2011migration.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Freeze item additions.<\/li>\n\n\n\n<li>Export the item master list.<\/li>\n\n\n\n<li>Map item names one to one.<\/li>\n\n\n\n<li>Migrate player inventories in batches. Validate stack sizes and weights.<\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Example item mapping CSV<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">esx_name,qb_name,notes\nbread,bread,\nwater,water,\nlockpick,lockpick,\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"step-8-test-and-roll-out\">Step 8. Test and Roll Out<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Unit tests\n<ol class=\"wp-block-list\">\n<li>Test identifier lookups for a random set of players.<\/li>\n\n\n\n<li>Test money transfers, job changes, and vehicle ownership.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Gameplay tests\n<ol class=\"wp-block-list\">\n<li>Spawn players with old ESX identifiers and confirm auto mapping.<\/li>\n\n\n\n<li>Run a police duty flow, a store robbery, and a vehicle purchase.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Performance tests\n<ol class=\"wp-block-list\">\n<li>Use <code>resmon<\/code> to watch CPU and memory.<\/li>\n\n\n\n<li>Confirm DB query counts dropped after oxmysql conversion.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Rollout plan\n<ol class=\"wp-block-list\">\n<li>Move staging DB to production during a maintenance window.<\/li>\n\n\n\n<li>Announce a 60 minute downtime.<\/li>\n\n\n\n<li>Monitor logs for missing identifiers and foreign key errors.<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"troubleshooting\">Troubleshooting<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Duplicated citizens\n<ol class=\"wp-block-list\">\n<li>Cause. Running the migration twice.<\/li>\n\n\n\n<li>Fix. Enforce unique keys on <code>citizenid<\/code> and use <code>INSERT IGNORE<\/code> during seeding.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Missing vehicles\n<ol class=\"wp-block-list\">\n<li>Cause. Owner key mismatch between <code>owned_vehicles.owner<\/code> and <code>legacy_identifier_map.license<\/code>.<\/li>\n\n\n\n<li>Fix. Normalize owner values and re\u2011run the vehicle insert for the affected plates.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Players spawn without inventory\n<ol class=\"wp-block-list\">\n<li>Cause. Inventory migration skipped.<\/li>\n\n\n\n<li>Fix. Rebuild the inventory mapping and re\u2011import.<\/li>\n<\/ol>\n<\/li>\n\n\n\n<li>Scripts fail with <code>MySQL.Async<\/code> not found\n<ol class=\"wp-block-list\">\n<li>Cause. Script still depends on mysql-async.<\/li>\n\n\n\n<li>Fix. Replace calls with oxmysql and remove mysql-async from the server.<\/li>\n<\/ol>\n<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"cutover-checklist\">Cutover Checklist<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Back up production database with a timestamp.<\/li>\n\n\n\n<li>Stop the server and lock player joins.<\/li>\n\n\n\n<li>Restore the final staging dump to production.<\/li>\n\n\n\n<li>Deploy QBCore build with <code>qb-core<\/code>, <code>oxmysql<\/code>, <code>ox_lib<\/code> first in the ensure order.<\/li>\n\n\n\n<li>Run the identifier seeding script once.<\/li>\n\n\n\n<li>Enable converted scripts only when their queries are on oxmysql.<\/li>\n\n\n\n<li>Reopen the server and watch logs for 30 minutes.<\/li>\n\n\n\n<li>Post a rollback plan if critical errors appear.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"appendix-a-example-fxmanifest-for-migration-helper\">Appendix A. Example fxmanifest for migration helper<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">fx_version 'cerulean'\ngame 'gta5'\n\nlua54 'yes'\n\nserver_scripts {\n  '@oxmysql\/lib\/MySQL.lua',\n  '@ox_lib\/init.lua',\n  'server\/migrate_identifiers.lua'\n}\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"appendix-b-safe-json-helpers\">Appendix B. Safe JSON helpers<\/h2>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">local function safeDecode(jsonStr, fallback)\n  if type(jsonStr) ~= 'string' or jsonStr == '' then return fallback end\n  local ok, result = pcall(json.decode, jsonStr)\n  if not ok then return fallback end\n  return result\nend\n<\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-you-achieved\">What you achieved<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Stable player records keyed by <code>citizenid<\/code>.<\/li>\n\n\n\n<li>A clean oxmysql layer with prepared statements and awaits.<\/li>\n\n\n\n<li>ESX code ported to QBCore using ox_lib callbacks and utilities.<\/li>\n\n\n\n<li>A versioned plan you can repeat for future servers.<\/li>\n<\/ol>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\" \/>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"useful-links-inside-your-site\">Useful links inside your site<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Framework conversion hub. <a href=\"https:\/\/fivemx.com\/framework-conversion\">https:\/\/fivemx.com\/framework-conversion<\/a><\/li>\n\n\n\n<li>MySQL Async to oxmysql guide. <a href=\"https:\/\/fivemx.com\/mysql-async-to-oxmysql\">https:\/\/fivemx.com\/mysql-async-to-oxmysql<\/a><\/li>\n\n\n\n<li>SQL identifiers migration. <a href=\"https:\/\/fivemx.com\/sql-identifiers-migration\">https:\/\/fivemx.com\/sql-identifiers-migration<\/a><\/li>\n\n\n\n<li>Adapter patterns for script ports. <a href=\"https:\/\/fivemx.com\/adapter-patterns\">https:\/\/fivemx.com\/adapter-patterns<\/a><\/li>\n\n\n\n<li>QBCore install quickstart. <a href=\"https:\/\/fivemx.com\/how-to-install-qbcore\">https:\/\/fivemx.com\/how-to-install-qbcore<\/a><\/li>\n\n\n\n<li>Script conversion checklist. <a href=\"https:\/\/fivemx.com\/converting-fivem-scripts\">https:\/\/fivemx.com\/converting-fivem-scripts<\/a><\/li>\n\n\n\n<li>QBOX with ox stack overview. <a href=\"https:\/\/fivemx.com\/qbox-ox-stack\">https:\/\/fivemx.com\/qbox-ox-stack<\/a><\/li>\n\n\n\n<li><a class=\"wpil_keyword_link\" href=\"https:\/\/fivemx.com\/how-to-use-resmon-in-fivem-optimize-resources\/\" title=\"How to Use Resmon in FiveM (To Optimize Resources)\" data-wpil-keyword-link=\"linked\" data-wpil-monitor-id=\"1738\">Resmon<\/a> and performance. <a href=\"https:\/\/fivemx.com\/how-to-use-resmon-in-fivem-optimize-resources\">https:\/\/fivemx.com\/how-to-use-resmon-in-fivem-optimize-resources<\/a><\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"external-references\">External references<\/h2>\n\n\n\n<ol class=\"wp-block-list\">\n<li><a href=\"https:\/\/qbcore.net\/\" target=\"_blank\" rel=\"noopener\">QBCore framework<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/coxdocs.dev\/oxmysql\" target=\"_blank\" rel=\"noopener\">oxmysql documentation.<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/coxdocs.dev\/ox_lib\" target=\"_blank\" rel=\"noopener\">ox_lib documentation.<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/docs.fivem.net\/docs\/scripting-reference\/runtimes\/lua\/functions\/GetPlayerIdentifiers\/\" target=\"_blank\" rel=\"noopener\">CFX.re identifiers reference.<\/a><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>You want a clean switch from ESX to QBCore without losing data or breaking core systems. Follow this plan. You will finish with stable identifiers, oxmysql queries, and ox_lib powered code. Goal: move your server from ESX to QBCore with minimal downtime. Prerequisites Step 1. Make a Plan and a Rollback Point Step 2. Build [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":199543,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[2882,1902,1899],"tags":[],"class_list":["post-199542","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-framework-conversion","category-fivem-related","category-tutorials"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/199542","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=199542"}],"version-history":[{"count":2,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/199542\/revisions"}],"predecessor-version":[{"id":207153,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/posts\/199542\/revisions\/207153"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media\/199543"}],"wp:attachment":[{"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/media?parent=199542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/categories?post=199542"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/fivemx.com\/pt\/wp-json\/wp\/v2\/tags?post=199542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}