Skip to main content
If your framework is not es_extended, qb-core, or qbx_core, you can still run every Atlas resource by filling in the custom framework adapter. An adapter normalizes your framework to one fixed shape, so consumer scripts call Bridge.GetJob(src) and Bridge.AddMoney(src, 'bank', 500) without ever branching on which framework you run. This is the most important page for unsupported setups. Work through the contract once and every Atlas resource works.
Consumers always use Bridge.Framework (and its shortcuts like Bridge.GetJob) — never the raw Fw* exports the bridge generates internally. You only implement the adapter; the bridge wires the rest.

How it works

The bridge selects a framework provider with the atlas:framework convar and loads the matching adapter. When you set it to custom, the bridge loads your editable stub:
  • Server → editable/framework/server.lua
  • Client → editable/framework/client.lua
Both files live outside the escrow (they are meant to be edited). If your custom adapter fails to load — a syntax error or a runtime error at load time — the bridge falls back to the _default adapter so the server still boots. That fallback is why a half-finished adapter can look like “money silently fails”: the default adapter’s no-op methods are running, not yours.

The single-return rule

Every adapter method returns exactly one value. The bridge re-exposes each method as an export, and exports cross a Lua VM boundary where multi-return is lost. Returning value, err will silently drop the second value. Pick one return and stick to it.

Normalized shapes

Your methods must return these exact shapes so consumer scripts keep working unchanged:
Map your framework’s account names to cash / bank / dirty inside the adapter. If your framework has no dirty-money concept, return 0 for it.

Server contract

Implement these in editable/framework/server.lua. Required methods must return correct data for Atlas resources to function; optional methods can return a safe no-op (0 / false / {}) if your framework lacks the feature.
→ string
required
The player’s unique identifier (license, char id, etc.).
→ int[]
required
Source ids of all currently loaded players.
→ { first, last }
required
The player’s character name.
→ job
required
The normalized job shape { name, label, grade, gradeLabel, isBoss, onDuty }.
→ bool
required
Set the player’s job. Return true on success.
→ number
required
Balance of 'cash' | 'bank' | 'dirty'.
→ bool
required
Add money to an account. reason is an optional audit string. Return true on success.
→ bool
required
Remove money. Return false if the player has insufficient funds.
→ bool
required
Whether the player is a server admin.
→ int[]
required
Source ids of online players with that job.
→ bool
required
Set the player’s duty state. Return true on success.
→ number
Society (job) account balance. Return 0 if you have no society banking.
→ bool
Add to a society account. Return false if unsupported.
→ bool
Remove from a society account. Return false if unsupported.
→ bool
Register item as a usable item, calling onUse(src) when used. See Usable items.

Client contract

Implement these in editable/framework/client.lua:
→ { identifier, name, job, money? }
required
The local player’s data. name is { first, last }; job is the normalized job shape; money is optional.
→ job
required
The local player’s normalized job shape.
→ bool
required
Whether the local player is on duty.

Lifecycle events you must fire

The bridge does not poll your framework. You translate your framework’s events into the normalized bridge events so consumers can react. Fire these as the matching thing happens:
Forgetting these is the most common mistake. Without atlasBridge:playerLoaded, Atlas resources never initialize the player; without atlasBridge:jobChange, job-gated features never update.

Usable items

Usable-item registration lives on the framework adapter (not inventory), because most frameworks own item usage. Implement RegisterUsable(item, onUse) to register a usable with your framework that calls onUse(src). When a consumer registers a usable through Bridge.Inventory.RegisterUsableItem, the bridge calls your RegisterUsable with an onUse that fires a per-resource bus event:
You do not fire that event yourself — just make sure onUse(src) runs when the item is used.

Worked example

A complete, copy-pasteable server adapter for a fictional MyFramework, modeled on the real es_extended adapter: an account map, a normalizeJob helper, every contract method, and the three lifecycle translations.
And the matching client adapter:

Test it

1

Enable debug

Add setr atlas:debug "true" and setr atlas:framework "custom", then restart atlasBridge.
2

Confirm the adapter loaded

Look for [Framework] server adapter: custom in the console. If you instead see _default, your stub errored at load — fix the error and restart.
3

Read money and job back

From a test command, call Bridge.GetJob(src) and Bridge.GetMoney(src, 'bank') and confirm they return real values for a loaded player.
4

Buy or receive an item

Run a flow that removes money and grants an item, and confirm the notification, the balance change, and the item all land.
5

Turn debug off

Set setr atlas:debug "false" before production.

Pitfalls

Usually the custom adapter errored at load and the bridge fell back to _default, whose money methods are no-ops that return false. Check the console for server adapter: _default and fix the load error.
Adapter methods are single-return — exports drop the second value. Never return ok, err; return one value.
Consumers read job.name / job.grade / job.isBoss and the accounts cash / bank / dirty. If your framework uses different keys, normalize them inside the adapter — do not pass the raw framework table through.
If players never initialize or job-gated features never update, you likely forgot atlasBridge:playerLoaded or atlasBridge:jobChange. The bridge relies entirely on these.

Custom inventory adapter

Running an unsupported inventory too? Fill the inventory adapter next.