So migrierst du ESX → QBCore richtig
Lerne mit unserer Schritt-für-Schritt-Anleitung, wie du migrierst. Enthält stabile Identifikatoren, oxmysql-Abfragen und ox_lib. Vollständiges Tutorial für 2026.

Du möchtest einen sauberen Wechsel von ESX zu QBCore ohne Datenverlust oder den Ausfall von Kernsystemen. Folge diesem Plan. Du wirst am Ende stabile Identifikatoren, oxmysql-Abfragen und ox_lib-basierten Code haben.
Dieser Guide ist Teil unseres vollständigen FiveM-Frameworks-Guides, in dem wir ESX, QBCore und QBOX ausführlich vergleichen und dir helfen, das richtige zu wählen.
Ziel: Deinen Server mit minimalen Ausfallzeiten von ESX zu QBCore zu migrieren.
Überblick
- Voraussetzungen
- Schritt 1. Einen Plan und einen Rollback-Punkt erstellen
- Schritt 2. Eine saubere QBCore-Basis aufbauen
- Schritt 3. mysql-async durch oxmysql ersetzen
Voraussetzungen
- Tools
- GIT und ein separater Branch für die Migration.
- MariaDB oder MySQL 8 mit aktivierten vollständigen Backups.
- Ein Staging-Server, der die Produktion spiegelt.
- Server-Artifacts
- FXServer auf denselben Build wie Produktion aktualisiert.
- QBCore Basis-Framework und Standard-Ressourcen.
- Bibliotheken, die du verwenden wirst
-
oxmysqlfür die Datenbank. -
ox_libfür Callbacks, UI-Helfer und Utility-Wrapper.
Schritt 1. Einen Plan und einen Rollback-Punkt erstellen
- Produktionsänderungen einfrieren. Stoppt neue Skript-Installationen und DB-Schreibvorgänge, die nicht zum Testen erforderlich sind.
- Deine gesamte Datenbank als benannten Snapshot sichern.
- Dein Server-Repository branchen und einen dedizierten
migrate-esx-to-qbcore-Branch erstellen. - Ein Runbook schreiben. Enthält Befehle zum Starten und Stoppen des Staging-Servers, zur DB-Wiederherstellung und zur Ausführung von Health-Checks.
Schritt 2. Eine saubere QBCore-Basis aufbauen
- Eine frische QBCore-Basis auf Staging deployen.
- Nur das Wesentliche aktiviert lassen. Jobs, Inventare und benutzerdefinierte Skripte deaktivieren, bis nach der DB-Migration.
- Diese Ressourcen zuerst installieren und starten
-
-- ESX mysql-async
MySQL.Async.fetchAll('SELECT * FROM users WHERE identifier = @id', {['@id'] = identifier}, function(rows)
-- ...
end)
-- QBCore oxmysql
local rows = MySQL.query.await('SELECT * FROM players WHERE citizenid = ?', { citizenid })
-- rows ist eine Lua-Tabelle; nil und Längenprüfungen direkt behandeln
-- ESX scalar Beispiel
MySQL.Async.fetchScalar('SELECT COUNT(1) FROM owned_vehicles', {}, function(count)
-- ...
end)
-- oxmysql scalar
local count = MySQL.scalar.await('SELECT COUNT(1) FROM player_vehicles')
-- ESX insert
MySQL.Async.execute('INSERT INTO addon_account VALUES (@owner, @name, @money)', {
['@owner'] = identifier, ['@name'] = name, ['@money'] = money
})
-- oxmysql insert
MySQL.prepare.await('INSERT INTO player_accounts (citizenid, name, amount) VALUES (?, ?, ?)', { citizenid, name, amount })
Hinweise
- Bevorzuge
query.await,scalar.awaitundprepare.awaitfür sauberen Ablauf. - Verwende prepared statements für Schreibvorgänge.
Schritt 4. ESX-Datenstrukturen auf QBCore mappen
Du wirst Spieleridentitäten und Entities migrieren. Verwende diese Referenz zum Mappen von Tabellen.
ESX-Tabelle
Schlüsselspalte
QBCore-Tabelle
Schlüsselspalte
Hinweise
users
identifier
players
citizenid
Identifikatoren konvertieren und citizenid für jede Zeile erstellen
owned_vehicles
owner
player_vehicles
citizenid
Kennzeichenformatierung und JSON-Payloads konvertieren
datastore_data
owner
player_metadata
citizenid
Wenn du JSON speicherst, sorgfältig zusammenführen
addon_account_data
owner
player_accounts
citizenid
Kontonamen auf QBCore-Banking oder Cash mappen
addon_inventory_items
owner
player_inventories
citizenid
Wenn du zu ox_inventory wechselst, separat migrieren
Du kannst benutzerdefinierte Tabellen behalten. Passe nur Fremdschlüssel an, die ESX-Identifikatoren referenzieren.
Schritt 5. Identifikatoren stabilisieren
ESX speichert oft einen CFX-Identifikator wie `license:xxxx` oder historisch `steam:xxxx`. QBCore verwendet `citizenid` als stabilen Spielerschlüssel und behält die Laufzeit-Identifikatoren nur zur Authentifizierung.
Du wirst
- Eine
citizenidfür jeden Spieler erstellen. - Legacy-Identifikatoren mit dem neuen Datensatz verknüpfen.
- Eine Lookup-Tabelle für Support und Audits führen.
SQL-Bootstrap
Führe dies auf einer Kopie deiner ESX-Datenbank aus, um QBCore-Tabellen vorzubereiten.
-- 1) players-Tabelle erstellen, falls fehlend. An dein QBCore-Schema anpassen.
CREATE TABLE IF NOT EXISTS players (
citizenid VARCHAR(11) PRIMARY KEY,
license VARCHAR(64) UNIQUE,
identifiers JSON NOT NULL,
name VARCHAR(64),
charinfo JSON NOT NULL, metadata JSON NOT NULL, money JSON NOT NULL, job JSON NOT NULL,
position VARCHAR(128) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 2) Eine Hilfsfunktion in SQL mit einem deterministischen Generator wäre komplex.
-- Stattdessen das Mapping in einer separaten Tabelle vorbereiten und citizenid in Lua generieren.
CREATE TABLE IF NOT EXISTS legacy_identifier_map (
license VARCHAR(64) PRIMARY KEY,
steam VARCHAR(64) NULL,
fivem VARCHAR(64) NULL,
discord VARCHAR(64) NULL,
xbl VARCHAR(64) NULL,
liveid VARCHAR(64) NULL,
citizenid VARCHAR(11) UNIQUE
);
-- 3) Das Mapping aus ESX-Users befüllen
INSERT INTO legacy_identifier_map (license)
SELECT DISTINCT REPLACE(identifier, 'identifier:', '')
FROM users
WHERE identifier LIKE 'license:%' OR identifier LIKE 'steam:%';
citizenid generieren und Spieler in Lua einfügen
Einmal auf Staging ausführen. Zuerst sichern.
-- server/migrate_identifiers.lua
local QBCore = exports['qb-core']:GetCoreObject()
local function generateCitizenId()
local charset = {}
for c = 65, 90 do table.insert(charset, string.char(c)) end
for n = 48, 57 do table.insert(charset, string.char(n)) end
math.randomseed(GetGameTimer())
local id = {}
for i = 1, 11 do id[i] = charset[math.random(1, #charset)] end
return table.concat(id)
end
local rows = MySQL.query.await('SELECT license FROM legacy_identifier_map WHERE citizenid IS NULL')
for _, r in ipairs(rows) do
local citizenid = generateCitizenId()
MySQL.prepare.await('UPDATE legacy_identifier_map SET citizenid = ? WHERE license = ?', { citizenid, r.license })
end
-- Spieler aus ESX-Users aufbauen
local users = MySQL.query.await([[SELECT u.identifier, u.firstname, u.lastname, u.dateofbirth, u.sex, u.height
FROM users u]])
for _, u in ipairs(users) do
local license = u.identifier
local map = MySQL.single.await('SELECT citizenid FROM legacy_identifier_map WHERE license = ?', { license })
if map and map.citizenid then
local name = string.format('%s %s', u.firstname or 'John', u.lastname or 'Doe')
local charinfo = json.encode({ firstname = u.firstname, lastname = u.lastname, birthdate = u.dateofbirth, gender = u.sex, height = u.height })
local metadata = json.encode({ hunger = 100, thirst = 100 })
local money = json.encode({ cash = 0, bank = 0, crypto = 0 })
local job = json.encode({ name = 'unemployed', label = 'Unemployed', grade = { name = '0', level = 0 }})
MySQL.prepare.await('INSERT IGNORE INTO players (citizenid, license, identifiers, name, charinfo, metadata, money, job) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', {
map.citizenid, license,
json.encode({ license = license }),
name, charinfo, metadata, money, job
})
end
end
print('Identifikator-Migration abgeschlossen')
Fahrzeuge übertragen
INSERT IGNORE INTO player_vehicles (citizenid, plate, vehicle, garage, state)
SELECT m.citizenid,
UPPER(JSON_UNQUOTE(JSON_EXTRACT(v.vehicle, '$.plate'))),
v.vehicle,
'legion',
1 FROM owned_vehicles v
JOIN legacy_identifier_map m ON m.license = v.owner;
Zufällige Stichproben im Spiel validieren. Kennzeichenformate und Garagen überprüfen.
Schritt 6. ESX-Code mit ox_lib auf QBCore portieren
Ersetze die ESX-Runtime-API durch QBCore-Äquivalente. Verwende ox_lib für Callbacks und Benachrichtigungen.
Spielerobjekt
-- ESX
local xPlayer = ESX.GetPlayerFromId(src)
xPlayer.addMoney(100)
-- QBCore
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.AddMoney('cash', 100)
Jobs
-- ESX Job-Prüfung
if xPlayer.getJob().name == 'police' then
-- ...
end
-- QBCore Job-Prüfung
local job = Player.PlayerData.job
if job and job.name == 'police' then
-- ...
end
Callbacks und UI
-- ESX Server-Callback
ESX.RegisterServerCallback('resource:getData', function(source, cb)
cb({ ok = true })
end)
-- ox_lib Callback
lib.callback.register('resource:getData', function(source)
return { ok = true }
end)
-- Benachrichtigung
lib.notify(source, { title = 'Job', description = 'Beförderung gewährt', type = 'success' })
Befehle
-- ESX
RegisterCommand('pay', function(src, args)
local amount = tonumber(args[1]) or 0
xPlayer.removeMoney(amount)
end)
-- QBCore mit Berechtigungen
QBCore.Commands.Add('pay', 'Bargeld bezahlen', {{name = 'amount', help = 'Betrag'}}, false, function(src, args)
local amount = tonumber(args[1]) or 0
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.RemoveMoney('cash', amount)
end)
Schritt 7. Inventar und Items
Wenn du von es_extended-Inventaren zu qb-inventory oder ox_inventory wechselst, behandle dies als separate Teil-Migration.
- Item-Hinzufügungen einfrieren.
- Die Item-Masterliste exportieren.
- Item-Namen eins zu eins mappen.
- Spieler-Inventare in Batches migrieren. Stack-Größen und Gewichte validieren.
Beispiel Item-Mapping CSV
esx_name,qb_name,notes bread,bread, water,water, lockpick,lockpick,
Schritt 8. Testen und Ausrollen
- Unit-Tests
- Identifikator-Lookups für eine zufällige Auswahl von Spielern testen.
- Geldtransfers, Job-Änderungen und Fahrzeugbesitz testen.
- Gameplay-Tests
- Spieler mit alten ESX-Identifikatoren spawnen und automatisches Mapping bestätigen.
- Einen Polizeidienst-Flow, einen Ladenraub und einen Fahrzeugkauf durchführen.
- Performance-Tests
resmonverwenden, um CPU und Speicher zu beobachten.- Bestätigen, dass die DB-Abfrageanzahl nach der oxmysql-Konvertierung gesunken ist.
- Rollout-Plan
-
Staging-DB während eines Wartungsfensters in die Produktion verschieben.
-
60-minütige Ausfallzeit ankündigen.
-
Logs auf fehlende Identifikatoren und Fremdschlüsselfehler überwachen.
Fehlerbehebung
- Duplizierte Citizens
- Ursache: Migration wurde zweimal ausgeführt.
- Fix: Eindeutige Schlüssel auf
citizeniderzwingen undINSERT IGNOREbeim Seeding verwenden. - Fehlende Fahrzeuge
- Ursache: Owner-Schlüssel-Mismatch zwischen
owned_vehicles.ownerundlegacy_identifier_map.license. - Fix: Owner-Werte normalisieren und den Fahrzeug-Insert für die betroffenen Kennzeichen erneut ausführen.
- Spieler spawnen ohne Inventar
- Ursache: Inventar-Migration übersprungen.
- Fix: Inventar-Mapping neu aufbauen und reimportieren.
- Skripte schlagen mit
MySQL.Asyncnicht gefunden fehl-
Ursache: Skript hängt noch von mysql-async ab.
-
Fix: Aufrufe durch oxmysql ersetzen und mysql-async vom Server entfernen.
Cutover-Checkliste
- Produktionsdatenbank mit einem Zeitstempel sichern.
- Server stoppen und Spieler-Joins sperren.
- Den finalen Staging-Dump in die Produktion zurückspielen.
- QBCore-Build mit
qb-core,oxmysql,ox_libzuerst in der Ensure-Reihenfolge deployen. - Das Identifikator-Seeding-Skript einmal ausführen.
- Konvertierte Skripte nur aktivieren, wenn ihre Abfragen auf oxmysql sind.
- Server wieder öffnen und Logs 30 Minuten lang beobachten.
- Rollback-Plan posten, wenn kritische Fehler auftreten.
Anhang A. Beispiel-fxmanifest für Migrationshelfer
fx_version 'cerulean'
-
- Ursache: Owner-Schlüssel-Mismatch zwischen
- Duplizierte Citizens
-
game 'gta5'
lua54 'yes'
server_scripts {
'@oxmysql/lib/MySQL.lua',
'@ox_lib/init.lua',
'server/migrate_identifiers.lua'
}
Anhang B. Sichere JSON-Helfer
local function safeDecode(jsonStr, fallback)
if type(jsonStr) ~= 'string' or jsonStr == '' then return fallback end
local ok, result = pcall(json.decode, jsonStr)
if not ok then return fallback end
return result
end
Was du erreicht hast
- Stabile Spielerdatensätze, die durch
citizenidgekennzeichnet sind. - Eine saubere oxmysql-Schicht mit prepared statements und awaits.
- ESX-Code auf QBCore portiert mit ox_lib-Callbacks und Utilities.
- Einen versionierten Plan, den du für zukünftige Server wiederholen kannst.
Nützliche Links auf deiner Seite
- Framework-Konvertierungs-Hub. https://vertexmods.com/blog/converting-fivem-scripts
- MySQL Async zu oxmysql Guide. https://vertexmods.com/blog/mysql-async-to-oxmysql
- SQL-Identifikatoren-Migration. https://vertexmods.com/blog/sql-identifiers-migration
- Adapter-Patterns für Skript-Ports. https://vertexmods.com/blog/adapter-patterns
- QBCore-Install-Schnellstart. https://vertexmods.com/blog/how-to-install-qbcore
- Skript-Konvertierungs-Checkliste. https://vertexmods.com/blog/converting-fivem-scripts
- QBOX mit ox-Stack-Übersicht. https://vertexmods.com/blog/qbox-ox-stack
- Resmon und Performance. https://vertexmods.com/blog/how-to-use-resmon-in-fivem-optimize-resources

