Adapter-Patterns: ESX, QBCore & QBOX (Exports, Events & APIs)
Schritt-für-Schritt-Anleitung für FiveM Framework-Adapter. Mit Stubs, die Konflikte erkennen, bevor du deployst. Kompletter Guide für 2026.

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` 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)


