How Integrations Work#
The menu itself needs nothing but the game. Everything else is reached through a small, open layer in custom/, detected at start, and the console prints what it picked. A framework is only ever asked two things: what to put on the player card, and whether somebody may open the creator.
Layer | Supported | Without it |
|---|---|---|
Framework |
| Standalone: the player card keeps the FiveM name, health, armour and location, and the money, job and identity rows hide themselves. The creator is opened by ACE only, and saved places are kept against the Rockstar licence. |
Database |
| The menu opens and draws the bundled themes; nothing can be saved. |
Notifications |
| Messages go to the chat. |
Status (ESX) |
| Hunger, thirst and stress are hidden on ESX. QBCore and Qbox read them from the player's metadata. |
Blip names | The blip hook · the | The legend falls back to |
qs-smartphone | The Maps app | Nothing to connect: both use the same Rockstar atlas and calibration, and both use GTA's own waypoint, so the phone and the menu always agree. |
Every optional integration degrades quietly. A missing resource makes its part stand down rather than throw — which is why the
framework loadedandstorage driverlines in the console matter. They are the only place that tells you something is absent before a player does.
Frameworks#
Config.Framework is 'auto' by default and looks for qs-core, es_extended, qbx_core and qb-core, in that order. Set it to pin one, or to 'standalone' to switch the layer off:
Config.Framework = 'auto' -- 'auto' | 'qs' | 'esx' | 'qb' | 'standalone'Qbox and QBCore share the 'qb' adapter, which talks to whichever of the two is running. With 'auto', a framework that was not running yet when the resource started is looked for again a few seconds later and swapped in, so a start-order race does not leave a session on the standalone adapter.
What is read from each framework#
Field | ESX · qs-core | QBCore · Qbox |
|---|---|---|
Name |
|
|
Job · grade |
|
|
Cash · bank · black |
|
|
Identity | identifier, phone number, date of birth, sex, height | citizen id, phone, birth date, gender |
Hunger · thirst · stress |
| The player's metadata |
Saved places stored against | The player's identifier | The citizen id |
Creator groups | The player's group ( | The framework's permissions ( |
Adding your own framework#
Each adapter is a plain table of methods that returns itself, so adding a framework is copying a folder in custom/, renaming it, and setting Config.Framework to the folder's name.
custom/
├── client.lua -- picks the adapter, and owns Notify()
├── server.lua -- picks the adapter, and owns the creator's group check
├── blip_hook.lua -- included by your other resources
├── esx/{client,server}.lua
├── qb/{client,server}.lua -- QBCore and Qbox
├── qs/{client,server}.lua
└── standalone/{client,server}.luaSide | Methods it must answer |
|---|---|
Client |
|
Server |
|
getPlayer returns the one shape the menu understands:
function framework:getPlayer()
return {
name = 'John Doe',
job = 'Police', grade = 'Sergeant',
cash = 1500, bank = 42000, black = nil,
citizenId = 'ABC12345', phone = '555-0101',
dob = '1990-01-01', sex = 'm', height = 180,
hunger = 80, thirst = 65, stress = 10,
}
endLeave any field you cannot fill as
nilrather than zero. That is what makes a module hide the row instead of printing a confident$0.getIdentifierdecides who owns a saved place — return the character's id, not the account's, if places should follow characters.
The Database Driver#
Config.Database.driver pins a driver; 'auto' takes oxmysql if it is running, then mysql-async. The table names can be changed in the same block — useful when two servers share one database.
Config.Database = {
driver = 'auto', -- 'auto' | 'oxmysql' | 'mysql-async'
themesTable = 'qs_pausemenu_themes',
settingsTable = 'qs_pausemenu_settings',
blipsTable = 'qs_pausemenu_blips',
}Notifications#
The few messages the resource shows — no permission, the waypoint was set, the menu is still loading — go through Notify() in custom/client.lua. It uses qs-interface when it is running, ox_lib otherwise, and the chat as a last resort. To use another notification resource, change that one function:
-- custom/client.lua
function Notify(message, kind)
if not message or message == '' then return end
-- kind is 'error' or nil
exports['okokNotify']:Alert('Pause Menu', message, 5000, kind == 'error' and 'error' or 'info')
endBlip Names From Your Resources#
GTA lets a script set a blip's name but never read it back. With Config.Menu.readBlipNames on, the resource reads names from the game one at a time, which covers most servers with nothing to do. For blips it cannot read, or to switch reading off entirely, your resources can hand their names over directly.
Add one line to the fxmanifest.lua of every resource that creates blips, before its own client scripts. Every name that resource sets is picked up live — nothing else in it changes.
client_script '@qs-pausemenu/custom/blip_hook.lua'
client_scripts {
'client/main.lua',
}It catches the usual ways of naming a blip — BeginTextCommandSetBlipName with a string or a text label, and SetBlipNameFromTextFile — forgets a blip when it is removed, and hands every live name over again if qs-pausemenu restarts. It only works in Lua resources.
Call it yourself right after creating the blip — from Lua, JavaScript or C#:
local blip = AddBlipForCoord(215.8, -810.1, 30.7)
SetBlipSprite(blip, 357)
BeginTextCommandSetBlipName('STRING')
AddTextComponentSubstringPlayerName('Central Garage')
EndTextCommandSetBlipName(blip)
exports['qs-pausemenu']:SetBlipName(blip, 'Central Garage')Colour codes such as ~r~ or ^1 are cleaned out. Pass false as the name to forget it.
For blips you cannot touch, name them by their sprite id in shared/config.lua. It is the last resort before the icon's own name.
Config.BlipNames = {
[357] = 'Garage',
[71] = 'Barber',
[73] = 'Clothing Store',
}Saving a Player Before They Leave#
By default the Leave the server button drops the player with Config.Actions.disconnectReason. If your server needs to save something first, switch it to an event and handle the leave yourself:
-- shared/config.lua
Config.Actions.disconnect = 'event'
Config.Actions.disconnectEvent = 'qs-pausemenu:client:disconnect'-- In one of your client scripts
AddEventHandler('qs-pausemenu:client:disconnect', function()
TriggerServerEvent('myserver:saveAndDrop')
end)
-- In one of your server scripts
RegisterNetEvent('myserver:saveAndDrop', function()
local src = source
-- save the character here
DropPlayer(src, 'See you soon!')
end)The event is a client event, fired on the player who pressed the button. A client script cannot run quit on its own, so the actual disconnect has to come from the server, as above.



