Adapter Patterns: ESX, QBCore & QBOX (Exports, Events & APIs)
This is a FiveM Framework Adapter for scripters. Deliver a single resource that runs on ESX, QBCore, and QBOX by isolating framework-specific calls behind a lean adapter.

Einleitung: FiveM Framework Adapter – für Scripter

Dies ist ein FiveM Framework Adapter – für Scripter. Liefere eine einzige Ressource, die auf ESX, QBCore und QBOX läuft, indem du framework-spezifische Aufrufe hinter einem schlanken Adapter isolierst. Füge die shared/fw.lua und die framework-spezifischen Adapter in jede Ressource ein, rufe den stabilen Interface-Vertrag (FW.Player, FW.Job, FW.Money, FW.Inv, FW.Events) auf und halte die Geschäftslogik framework-agnostisch. Eine kleine Test-Matrix mit Stubs erkennt Konflikte, bevor du deployst.
Warum ein Adapter?
Framework-Unterschiede konzentrieren sich auf dieselben Stellen:
- Core Zugriff (ESX
getSharedObject, QBCoreGetCoreObject, QBOX nur Exports) - Player-Model (xPlayer vs Player/PlayerData)
- Identifiers (license/steam vs citizenid)
- Money & Inventory APIs
- Event-Namen bei Load/Login/Job-Update
Ein einheitliches Interface hält diese Unterschiede aus deiner Spiellogik heraus. Du tauschst den Adapter aus, nicht die Codebasis.
BTW: Unseren fertigen Adapter kannst du hier kostenlos nutzen:
Verwendung (Drop-in)
Verzeichnisstruktur (empfohlen):
my-resource/ ├─ fxmanifest.lua ├─ shared/ │ ├─ adapters/ │ │ ├─ esx.lua │ │ ├─ qb.lua │ │ └─ qbox.lua │ └─ fw.lua ├─ server/ │ └─ main.lua └─ client/ └─ main.lua
fxmanifest.lua (Adapter zuerst laden, dann fw.lua, damit die Erkennung binden kann):
| fx_version 'cerulean' | game 'gta5' lua54 'yes' |
|---|---|
| shared_scripts { lua | 'shared/adapters/*.lua', 'shared/fw.lua' } |
| client_scripts { ```lua 'client/*.lua' } | server_scripts { lua |
'@oxmysql/lib/MySQL.lua', -- optional: falls du SQL verwendest
'server/*.lua'
}
**In deinem Code** (Server oder Client):
```lua
```lua
-- überall das stabile Interface verwenden
local src = source
local p = FW.Player.getBySrc(src)
local job = FW.Job.getName(p)
FW.Money.add(p, 'cash', 250, 'lieferbonus')
FW.Inv.addItem(p, 'water', 1)
FW.Events.notify(src, 'Job-Bonus ausgezahlt.', 'success')
> Das einzige Symbol, von dem du abhängst, ist `FW`. Alles andere ist intern für die **Adapter**.
* * *
## Interface-Vertrag (stabiler Oberflächenbereich)
Designziel: **Klein, explizit, dokumentiert.** Dies sind die Funktionen, auf die du framework-übergreifend zählen kannst.
### `FW.meta`
* `name() -> 'esx'|'qbcore'|'qbox'`
* `has(resourceName: string) -> boolean` (Ressource gestartet?)
### `FW.Player`
* `getBySrc(src: number) -> any` (Framework-Player-Handle)
* `getStateId(p) -> string` (ESX: identifier; QB/QBOX: citizenid)
* `getServerId(p) -> number` (numerische ID)
* `getName(p) -> string`
### `FW.Job`
* `getName(p) -> string`
* `getGrade(p) -> number|string`
* `onChange(handler(src, oldJob, newJob))` (feuert bei Job-Wechsel, falls erkennbar)
### `FW.Money`
* `get(p, account: 'cash'|'bank'|'black_money'?) -> number`
* `add(p, account, amount: number, reason?: string)`
* `remove(p, account, amount: number, reason?: string)`
### `FW.Inv` (Best-Effort; siehe Hinweise)
* `addItem(p, name: string, count: number, metadata?: table) -> boolean`
* `removeItem(p, name: string, count: number, metadata?: table) -> boolean`
> ## Inventar-Hinweis: Server variieren (qb-inventory,
>
> **Inventar-Hinweis:** Server variieren (qb-inventory, ox_inventory, qs‑inventory usw.). Die Standard-Implementierung nutzt das Framework-Inventar, falls verfügbar, und fällt auf [`ox_inventory`](https://overextended.dev/ox_inventory) zurück, wenn erkannt.
### `FW.Events`
* `notify(target: number, msg: string, type?: 'info'|'success'|'error')`
* `onPlayerLoaded(handler(src))` (Best-Effort, mit Fallback über `playerJoining`)
* * *
## Drop-in-Adapter (Copy/Paste)
Dies sind pragmatische Standardwerte. Falls dein Fork abweicht (besonders bei QBOX), passe die wenigen markierten Kommentare an.
### `shared/fw.lua`
```lua
-- Framework Bridge Bootstrap
FW = FW or {}
local function started(name)
local st = GetResourceState(name)
return st == 'started' or st == 'starting'
end
local which
if started('qbx_core') then which = 'qbox'
elseif started('qb-core') then which = 'qbcore'
elseif started('es_extended') then which = 'esx' end
if which == 'qbcore' then
FW = Adapters.qb()
elseif which == 'qbox' then
FW = Adapters.qbox()
elseif which == 'esx' then
FW = Adapters.esx()
else
error('[FW\] Kein unterstütztes Framework gefunden (es_extended / qb-core / qbx_core).')
end
-- kleine Hilfsfunktionen, die für alle Adapter gelten
function FW.meta.has(res)
return started(res)
end
shared/adapters/esx.lua, qb.lua, qbox.lua
Die vollständigen Adapter-Implementierungen sind identisch mit dem Original — nur die Kommentare wurden auf Deutsch übersetzt. Alle Lua-Funktionsnamen, Event-Namen und API-Aufrufe bleiben unverändert.
Verwendungsbeispiele
1) Job-Bonus auszahlen
RegisterNetEvent('myres:payBonus', function()
local src = source
local p = FW.Player.getBySrc(src)
if not p then return end
if FW.Job.getName(p) == 'delivery' then
FW.Money.add(p, 'cash', 250, 'lieferbonus')
FW.Events.notify(src, 'Bonus ausgezahlt (+$250).', 'success')
else
FW.Events.notify(src, 'Du bist nicht als Lieferant im Dienst.', 'error')
end
end)
2) Inventar-Vergabe mit automatischem ox-Fallback
local function giveStarter(src)
local p = FW.Player.getBySrc(src)
if p then FW.Inv.addItem(p, 'water', 2) end
end
FW.Events.onPlayerLoaded(giveStarter)
Anti-Pattern-Katalog (und Lösungen)
| Anti-Pattern | Warum es schadet | Lösung mit Adapter |
|---|---|---|
Core-Objekt hardcoden (ESX = exports['es_extended']:getSharedObject() überall verstreut) | Bindet an ESX, mühsam zu migrieren | Nur FW.* aufrufen. Core-Auflösung liegt im Adapter. |
| Framework-Player-Handle langfristig speichern | Handles können veralten; Referenzen variieren je Framework | Per FW.Player.getBySrc(src) beim Handeln neu holen oder per getStateId-Key cachen und neu auflösen. |
Identifier-Annahmen treffen (ESX identifier vs QB/QBOX citizenid) | Bricht DB-Relationen/Migrationen | FW.Player.getStateId(p) und eine Crosswalk-Tabelle bei Migrationen verwenden. |
Direkte Event-Namen in der Geschäftslogik (esx:playerLoaded, QBCore:Server:PlayerLoaded) | Fragil bei Forks | Über FW.Events.onPlayerLoaded abonnieren. |
| Gemischte Inventar-Annahmen | Server tauschen Inventare oft | FW.Inv.* verwenden, das zuerst ox_inventory erkennt, dann das Framework. |
Implementierungs-Checkliste
shared/adapters/*.luaundshared/fw.luain deine Ressource einbinden- Alle direkten ESX/QBCore/QBOX-Aufrufe im Code durch
FW.*ersetzen - Nur einen Persistenz-Key verwenden:
state_idin deinen Tabellen - Inventar-Präferenz konfigurieren (ox standardmäßig zuerst)
- CI hinzufügen (luacheck + busted) und minimalen Test für jeden verwendeten Aufruf
- Lokale Abweichungen (fork-spezifische Events) oben in deiner Adapter-Datei dokumentieren
Abschließende Hinweise
- Adapter langweilig halten: keine Seiteneffekte, keine Datenbankaufrufe.
- Framework-Handles als undurchsichtig behandeln; nur das durch den Vertrag Benötigte extrahieren.
- Wenn du für einen Client abweichen musst, den Adapter kopieren, nicht die Geschäftslogik.
Als nächstes lesen: FiveM Scripts zwischen ESX, QBCore & QBOX konvertieren (Pillar Page)
Verwandte Artikel
- ESX vs QBCore vs QBOX: Technischer Framework-Vergleich 2026
- FiveM Frameworks erklärt: Vollständiger Guide zu ESX, QBCore & QBOX
- FiveM Scripts konvertieren: ESX, QBCore und QBOX (Framework-Guide)
- QBCore zu QBOX Migration: Vollständige Schritt-für-Schritt-Anleitung
Entdecke unsere Premium FiveM Mods und die kostenlose Mods-Sammlung für sofort einsetzbare Ressourcen.
Detaillierte Konfigurationsbeispiele
Dieser Abschnitt zeigt, wie du den Adapter in verschiedenen Szenarien konfigurierst und anpasst.
Beispiel 1: ox_inventory Priorität erzwingen
Standardmäßig erkennt der Adapter ox_inventory automatisch und verwendet es. Wenn du die Priorität explizit festlegen möchtest (z.B. wenn sowohl ox_inventory als auch das Framework-eigene Inventar installiert sind), kannst du dies in shared/fw.lua tun:
-- In shared/fw.lua, vor dem Adapter-Bootstrap
FW = FW or {}
FW.config = FW.config or {}
FW.config.inventoryPriority = 'ox' -- oder 'framework', um das Framework-Inventar zu priorisieren
-- Framework Bridge Bootstrap (wie gehabt)
local function started(name)
local st = GetResourceState(name)
return st == 'started' or st == 'starting'
end
local which
if started('qbx_core') then which = 'qbox'
elseif started('qb-core') then which = 'qbcore'
elseif started('es_extended') then which = 'esx' end
if which == 'qbcore' then
FW = Adapters.qb()
elseif which == 'qbox' then
FW = Adapters.qbox()
elseif which == 'esx' then
FW = Adapters.esx()
else
error('[FW] Kein unterstütztes Framework gefunden (es_extended / qb-core / qbx_core).')
end
-- kleine Hilfsfunktionen, die für alle Adapter gelten
function FW.meta.has(res)
return started(res)
end
Beispiel 2: Eigene Benachrichtigungsfunktion verwenden
Anstatt die standardmäßige FW.Events.notify-Funktion zu verwenden, möchtest du vielleicht dein eigenes Benachrichtigungssystem integrieren. Dazu kannst du die Funktion im entsprechenden Adapter überschreiben:
-- In shared/adapters/esx.lua (oder qb.lua/qbox.lua)
local M = {}
-- Hier die Standard-Implementierungen...
function M.Events.notify(target, msg, type)
-- Deine eigene Benachrichtigungslogik hier
-- Beispiel:
TriggerClientEvent('my_custom_notify', target, msg, type)
end
return M
Stelle sicher, dass du die clientseitige Komponente my_custom_notify entsprechend implementierst.
Beispiel 3: Anpassung der Job-Wechsel-Erkennung
Die FW.Job.onChange-Erkennung basiert auf Framework-spezifischen Events. Wenn dein Server diese Events verändert hat oder andere Events verwendet, musst du die Adapter entsprechend anpassen.
-- In shared/adapters/qb.lua (oder esx.lua/qbox.lua)
local M = {}
-- Hier die Standard-Implementierungen...
function M.Job.onChange(handler)
-- Achtung: 'QBCore:Server:PlayerJobUpdate' ist ein Beispiel. Ersetze es, falls nötig.
RegisterNetEvent('QBCore:Server:PlayerJobUpdate')
AddEventHandler('QBCore:Server:PlayerJobUpdate', function(playerData)
local src = tonumber(playerData.source)
local p = FW.Player.getBySrc(src)
if not p then return end
local oldJob = playerData.job.name
local newJob = M.Job.getName(p) -- Annahme: M.Job.getName liefert den aktuellen Job
handler(src, oldJob, newJob)
end)
end
return M
Fehlerbehebung
Dieser Abschnitt behandelt häufige Probleme bei der Verwendung des Framework-Adapters und bietet Lösungen.
Problem 1: "attempt to index a nil value" beim Aufrufen von FW.*
Dieser Fehler tritt auf, wenn der Adapter nicht korrekt initialisiert wurde. Überprüfe Folgendes:
- Reihenfolge der Ressourcendarstellung: Stelle sicher, dass die Ressourcen mit den Adaptern und
fw.luavor deinen eigenen Ressourcen gestartet werden. Die fxmanifest.lua sollte die Adapterdateien vor den Skriptdateien der Ressource laden! Starte ggf. die Ressource im Server neu. - Framework-Erkennung: Überprüfe, ob das korrekte Framework (ESX, QBCore, QBOX) gestartet ist. Der Adapter gibt eine Fehlermeldung im Server-Log aus, wenn kein Framework erkannt wird.
- Tippfehler: Stelle sicher, dass du
FW.*korrekt geschrieben hast und nicht versehentlich den Namen einer Variable verändert hast.
Problem 2: Falsche Spielerdaten (z.B. falscher Job)
Dieser Fehler tritt häufig auf, wenn die Player-Handle, die vom Framework bereitgestellt wird, veraltet ist. Verwende niemals gecachte Player-Handles über längere Zeit. Stattdessen:
- Verwende
FW.Player.getBySrc(src)jedes Mal dann, wenn du auf Spielerdaten zugreifen musst. Dadurch stellst du sicher, dass du immer eine aktuelle Referenz hast. - Für komplexere Fälle, in denen du Daten zwischenspeichern musst, verwende
FW.Player.getStateId(p)als Schlüssel und löse die Player-Handle bei Bedarf mitgetBySrcneu auf.
Problem 3: Inventarfunktionen funktionieren nicht
Überprüfe Folgendes, wenn FW.Inv.* nicht wie erwartet funktioniert:
ox_inventoryinstalliert? Wenn duox_inventoryverwendest, stelle sicher, dass es korrekt installiert und gestartet ist.- Inventar-Priorität: Überprüfe die Einstellung
FW.config.inventoryPriorityinshared/fw.lua, um sicherzustellen, dass die korrekte Inventar-Implementierung priorisiert wird. - Fehlermeldungen: Achte auf Fehlermeldungen im Server-Log. Diese können Hinweise darauf geben, ob
ox_inventorynicht gefunden wurde oder es Probleme mit den Inventar-Exports gibt.
Problem 4: Job-Wechsel-Event wird nicht ausgelöst
- Event-Namen: Vergewissere dich, dass der im Adapter konfigurierte Event-Name für Job-Wechsel korrekt ist (siehe Beispiel 3 unter "Detaillierte Konfigurationsbeispiele").
- Event-Trigger: Stelle sicher, dass das entsprechende Framework-Event tatsächlich ausgelöst wird, wenn ein Spieler seinen Job wechselt. Dies ist oft eine Server-seitige Konfiguration im Core-Framework oder Job-Ressourcen.
Vergleich mit alternativen Scripten
Es gibt alternative Ansätze, um die Framework-Abhängigkeit deiner Scripte zu reduzieren. Hier ein kurzer Vergleich:
- Exports direkt wrappen: Anstatt einen dedizierten Adapter zu verwenden, könntest du Exports und Events direkt in deinem Script wrappen. Dies ist eine schnelle Lösung, führt aber zu Code-Duplizierung und macht die Wartung schwieriger. Der Framework-Adapter zentralisiert diese Wrappers und bietet ein konsistentes Interface.
-- Beispiel: Direkter Wrapper (Anti-Pattern)
local function giveMoney(src, amount)
if GetResourceState('es_extended') == 'started' then
local xPlayer = ESX.GetPlayerFromId(src)
xPlayer.addMoney(amount)
elseif GetResourceState('qb-core') == 'started' then
local Player = QBCore.Functions.GetPlayer(src)
Player.Functions.AddMoney('cash', amount)
end
end
-
Message Bus: Eine Message Bus (wie
npwd) ermöglicht die Kommunikation zwischen Ressourcen ohne direkte Abhängigkeit. Dies ist ein flexibler Ansatz, erfordert aber eine komplexere Konfiguration und kann die Performance beeinträchtigen. Der Framework-Adapter ist einfacher einzurichten und optimiert für den direkten Zugriff auf Framework-Funktionen. -
Abstrakte Klassen/Interfaces (OOP): Fortgeschrittene Entwickler könnten versuchen, abstrakte Klassen oder Interfaces in Lua zu verwenden, um die Framework-Abhängigkeit zu abstrahieren. Dies kann zu sauberem Code führen, ist aber komplex und erfordert ein fundiertes Verständnis von objektorientierter Programmierung in Lua. Der Framework-Adapter bietet eine pragmatische, funktionsorientierte Lösung, die für die meisten Anwendungsfälle ausreichend ist.
Erweiterte Anwendungstipps:
- Asynchrone Aufgaben: Wenn du asynchrone Aufgaben (z.B. Datenbankabfragen) in Verbindung mit dem Framework-Adapter verwendest, achte besonders auf die Gültigkeit der Player-Handles. Verwende, wie bereits erwähnt,
state_idals Schlüssel und löse die Player-Handles neu auf, bevor du auf Spielerdaten zugreifst. - Middleware: Du kannst Middleware-Funktionen verwenden, um die Aufrufe an
FW.*zu protokollieren oder zu validieren. Dies kann hilfreich sein, um Fehler zu finden oder sicherzustellen, dass die Daten konsistent sind.
-- Beispiel: Middleware für Money.add
local originalAdd = FW.Money.add
FW.Money.add = function(p, account, amount, reason)
print(string.format("Spieler %s gibt %d %s aufgrund von %s.", FW.Player.getName(p), amount, account, reason))
return originalAdd(p, account, amount, reason)
end
- Testen: Schreibe Unit-Tests für deine Scripts, um sicherzustellen, dass sie korrekt funktionieren, unabhängig vom zugrunde liegenden Framework. Verwende Stubs und Mocks, um die
FW.*-Funktionen zu simulieren und verschiedene Szenarien zu testen. Dies hilft, Regressionen zu vermeiden und die Codequalität zu verbessern. - Framework-spezifische Erweiterungen: Obwohl das Ziel des Adapters ist, Framework-Agnostizismus zu erreichen, kann es in einigen Fällen erforderlich sein, auf Framework-spezifische Funktionen zuzugreifen. In diesen Fällen kannst du
FW.meta.name()verwenden, um das aktuelle Framework zu ermitteln und bedingt auf dessen Funktionen zuzugreifen. Dies sollte jedoch nur als letztes Mittel verwendet werden, um die Abhängigkeit vom Framework so gering wie möglich zu halten.
Framework-spezifische Hinweise (ESX/QBCore/QBox)
- ESX:
- Achte auf die Version von ESX, die du verwendest. Einige APIs und Event-Namen können sich zwischen den Versionen unterscheiden.
- Das Inventar-System in ESX kann stark angepasst sein. Überprüfe, ob die Standard-Implementierung im Adapter mit deiner ESX-Konfiguration kompatibel ist.
- QBCore:
- QBCore verwendet ein modulares System, bei dem viele Funktionen in separaten Ressourcen implementiert sind. Stelle sicher, dass die erforderlichen Ressourcen (z.B.
qb-inventory) gestartet sind, bevor du auf deren Funktionen zugreifst. - Die
QBCore.Functions.GetPlayerFunktion gibt ein Player-Objekt zurück. Achte auf die Namenskonvention (gross geschrieben am Anfang).
- QBCore verwendet ein modulares System, bei dem viele Funktionen in separaten Ressourcen implementiert sind. Stelle sicher, dass die erforderlichen Ressourcen (z.B.
- QBox:
- QBox ist eine Weiterentwicklung von QBCore und teilt viele Konzepte. Die meisten Hinweise zu QBCore gelten auch für QBox.
- QBox verwendet oft Exports, um Funktionen bereitzustellen. Stelle sicher, dass die entsprechenden Exports verfügbar sind, bevor du sie aufrufst.
Durch Beachtung dieser Punkte kannst du sicherstellen, dass deine Scripte reibungslos auf allen drei Frameworks laufen.
