How Integrations Work#
Nothing inside the wheel knows which framework you run. It asks one small object, RadialBridge, who the player is — their job, gang, money, items — and a file per framework in custom/ answers. Every one of those files is open, so supporting a fork or a renamed core is an edit, not a ticket.
Layer | Supported | Without it |
|---|---|---|
Framework |
| Runs standalone. There are no jobs or gangs, so restricted nodes stay hidden. |
Jobs and gangs | The framework's player data, with the grade as a number. Gangs on QBCore and Qbox, and on ESX or vRP when the server provides them. | Job- and gang-restricted nodes hidden. |
Duty | QBCore and Qbox | Everyone counts as on duty. |
Money | ESX accounts and QBCore money — | Reads 0, so money conditions never pass. |
Items |
| Item conditions never pass. |
Notifications |
| Printed to the F8 console. |
Admins | ESX groups, QBCore and Qbox permissions, vRP groups. | ACE and |
Database |
| Required. The only one. |
Every optional layer degrades quietly. A missing framework or inventory makes the buttons that depend on it stay hidden — nothing errors, and everything else keeps working.
Detection and Overriding It#
config/main.lua looks for started resources when Radial Menu starts, and picks the bridge from the first one it finds:
-- Auto, the default
Config.Framework = DependencyCheck({
['es_extended'] = 'esx',
['qb-core'] = 'qb',
['qbx_core'] = 'qb',
['vrp'] = 'vrp',
}) or 'standalone'
-- Pinned: replace the whole expression
Config.Framework = 'qb'Pin it when two frameworks run at once — the list has no priority order — when your core runs under another resource name, or to try the menu standalone. The choice is printed in both consoles at start: Server framework loaded: qb.
The Bridge#
These are the questions the menu asks. Use them yourself in canInteract functions and conditions, instead of calling the framework, and they keep working if the server ever changes framework:
Function | Returns |
|---|---|
|
|
|
|
| Whether the player is on duty. |
| The balance of |
| Whether the player carries at least |
| How many they carry. |
| The player's state, read from the game. |
| The ESX identifier or QBCore citizen ID, and the character's name. |
| Shows a notification through the framework. |
The bridge also tells the menu when to rebuild. The wheel is rebuilt after the framework's own player-loaded, job and gang events — esx:setJob, QBCore:Client:OnJobUpdate, QBCore:Client:OnGangUpdate and their relatives — so a promotion or a new gang shows without the player doing anything. vRP has no reliable job event, so its bridge also checks the job once a second.
Writing Your Own Actions and Conditions#
custom/registry.lua loads after the built-in library and is yours to edit. Whatever you register there appears in the /radialedit dropdowns, ready to attach to any button:
local reg = RadialEditorRegistry
-- An action that runs code
reg.RegisterAction({
id = 'myserver.repair',
label = 'Repair current vehicle',
fn = function()
local veh = GetVehiclePedIsIn(PlayerPedId(), false)
if veh ~= 0 then SetVehicleFixed(veh) end
end,
})
-- An action that is just an event or a command
reg.RegisterAction({
id = 'myserver.billing',
label = 'Open billing',
type = 'client', -- client | server | command | qbcommand
event = 'billing:client:open',
})
-- A condition: return true to show the node
reg.RegisterPredicate({
id = 'myserver.hasRadio',
label = 'Carrying a radio',
fn = function()
return RadialBridge.HasItem('radio')
end,
})An action needs either fn, or a type and an event. A condition is always a function. Registering an ID that already exists replaces it. Restart the resource to load changes to the file. Conditions run every time the wheel opens, so keep them as cheap as canInteract.
Actions that are only an event or a command don't need Lua at all: create them in /radialedit → Actions and they are stored in the database.
Letting Other Scripts Add Buttons#
Another resource adds its own section with two exports, without touching config/menu.lua:
-- client side of your resource
exports['qs-radialmenu']:CreateCategory('mystore', { label = 'My Store', icon = 'Store' })
exports['qs-radialmenu']:CreateItem('mystore', {
label = 'Open shop', icon = 'ShoppingCart',
event = 'mystore:client:open', eventType = 'client',
})Functions cannot travel between resources, so from another resource a button runs an event or a command, and who sees it is decided by job and gang. action, canInteract and fn functions only work in Radial Menu's own files. When the resource stops or restarts, everything it added disappears from the wheel, and its start code adds it back — no cleanup needed. Every option is in Commands and Exports.
Scripts written for other radial menus#
AddOption takes the familiar option table — id, title, icon, type, event, shouldClose — used by qb-radialmenu integrations, puts it at the top level of the wheel and returns its ID for RemoveOption. Point the call at exports['qs-radialmenu'] and it works the same way. A few common Font Awesome icon names are translated; for the rest, use a Lucide name.
Quasar Resources in the Default Menu#
The shipped menu works with these resources out of the box:
Resource | Section |
|---|---|
| Apartments, shown inside an apartment. |
| Garages, for the mechanic and realestate jobs. |
| Laboratory, shown inside a lab. |
| Motorhome, shown in or near a van. |
Other buttons run commands such as /configure_hud, /simcards, /casino or /openracingui, which belong to the resources that provide them.
Adding a Framework#
Supporting a core that is not on the list is deliberately small: two files and one line.
Copy the standalone bridge
Copy
custom/standalone/tocustom/<name>/and change the guard on the first line of both files to your name:lua if Config.Framework ~= 'mycore' then return endAnswer what your core knows
In
client.lua, override theRadialBridgefunctions your core can answer —GetJobandOnJobChangefirst, ascustom/qb/client.luadoes. Inserver.lua, makeRadialBridgeServer.IsAdmin(source)return true for your staff. Anything you leave out keeps the standalone answer.Load it and select it
Add both files to
fxmanifest.luanext to the other bridges — the client file underclient_scripts, the server file underserver_scripts— and setConfig.Framework = 'mycore', or add your core to theDependencyChecklist.
Languages#
41 translations ship in locales/: ar bg bn ca cs da de el en es et fa fi fr he hi hr hu id it ja ko lt lv ms nl no pl pt ro ru sk sl sv sw th tr uk ur vi zh. Config.Locale picks one, and any text missing from it falls back to English.
Each file holds the interface texts and a menu block with the labels of the default menu. To add a language, copy en.json to a new code, translate the values — never the keys — and set Config.Locale to it. The Back, Home and "Press ESC to close" texts can also be set directly with Config.ButtonTexts = { back = '…', home = '…', closeInstruction = '…' }.



