Everything that depends on another resource sits behind a small, open-source bridge file. Interface picks the bridge when it starts, by looking for the first resource on its list that is already running.
Layer
Supported
Without it
Framework
es_extended · qb-core (Qbox through its qb-core layer)
Required.
Hunger and thirst
esx_status · QBCore metadata
Both stay at 0.
Voice
pma-voice · saltychat
Talking is shown; the range stays at the shortest.
Fuel
lc_fuel · qs-fuelstations
The vehicle's native fuel level — correct for any fuel script that sets it.
Multicharacter
qs-multicharacter
The HUD starts on the framework's player-loaded event.
Loading screen
qs-loadingscreen
The game's loading screen is shut down when the first-time setup opens.
Seatbelt sounds
InteractSound (carbuckle, carunbuckle)
Silent buckle; the notification still shows.
Log screenshots
screenshot-basic + a Fivemanage token
Logs are sent without a picture.
VIP purchases
Tebex, through sv_tebexSecret
/redeemvip refuses every code; admins can still give tags.
Detection happens once, at start, and only sees resources that are already running. Start your voice and fuel resources before qs-interface, or that part silently runs standalone.
Each list is a small table in shared/config.lua, mapping a resource name to a bridge. A renamed framework or voice resource only needs its new name added:
lua
local frameworks = {
['es_extended'] = 'esx',
['qb-core'] = 'qb',
['my-renamed-core'] = 'qb', -- added
}
-- or skip detection and force a bridge:
Config.Fuel = 'lc_fuel'
The same applies to voiceChatList, fuels and multicharacters.
A fuel bridge is one function, GetFuel(vehicle), returning 0–100. Most fuel scripts already write the native level and need nothing. For one that keeps fuel elsewhere — ox_fuel's state bag, for example — create a file next to the others:
lua
-- client/custom/fuel/ox_fuel.luaif Config.Fuel ~= 'ox_fuel'thenreturnendfunctionGetFuel(vehicle)return Entity(vehicle).state.fuel or GetVehicleFuelLevel(vehicle)
end
then add ['ox_fuel'] = 'ox_fuel' to the fuels table and restart.
A voice bridge, in client/custom/voices/, is two functions: IsTalking() and GetProximityDistance(), which returns 1.5, 3 or 6 — whisper, normal and shout — to size the voice ring. Copy pma.lua as the starting point.
custom/esx/ and custom/qb/ hold everything Interface asks the framework: money, job, status, names, stress and who is an admin. They are unencrypted and commented, so a modified core is supported by editing the matching function:
You want to change
Edit
Who counts as staff
playerIsAdmin in server.lua
Where hunger and thirst come from
getStatus and its events in client.lua
The roleplay name in chat
getUserName in server.lua
Which jobs appear in Job Channels
getJobsData in server.lua
Where stress is stored
getStress, updateStress in server.lua
What relieves stress
The events at the bottom of client.lua
Out of the box, stress is relieved by esx_status:add, esx_basicneeds:healPlayer and death on ESX, and by eating, drinking, alcohol, hospital respawn and revive on QBCore.
Route your framework's own functions through Interface and every script on the server uses the player's chosen styles. On QBCore, replace QBCore.Functions.Progressbar in qb-core/client/functions.lua:
Notifications follow the same idea: point your framework's notify function at exports['qs-interface']:AddNotify(text, title, time, icon). Keep a copy of the originals — framework updates overwrite these files.
Interface listens to the same events as qb-hud, so drugs, robberies or gyms written for it raise and lower stress without changes:
lua
-- client side
TriggerServerEvent('hud:server:GainStress', math.random(2, 4))
TriggerServerEvent('hud:server:RelieveStress', 25)
Gaining only works with Config.Stress = true and never for a job in Config.DisableJobsStress; relieving always works. Values are kept between 0 and 100.
Every chat message, admin action and VIP change can be posted to Discord. Set one webhook as the fallback and override the channels you want separated:
lua
-- server/webhooks.lua
webhook = {
chat = 'https://discord.com/api/webhooks/...', -- fallback for everything below
chat_me = '',
chat_do = '',
chat_ooc = '',
chat_anon = '', -- logs the real sender of anonymous messages
chat_admin = '',
chat_report = '',
chat_dice = '',
chat_rps = '',
chat_message = '', -- messages typed without a command
chat_job = '', -- job channels
chat_admin_action = '', -- /chatadmin: saves, clears, announcements
chat_vip = '', -- VIP granted, removed and redeemed
}
fivemanage = { webhook = '' } -- Fivemanage token, adds a screenshot to logs
Config.ShowWebhookIP = false-- true includes the player's IP
Each log names the player with their identifiers. Logs are queued and sent in order, respecting Discord's rate limits.
Add your Tebex secret key to server.cfg and sell one package per tier. After buying, the player types /redeemvip tbx-xxxx <tier>; the transaction is checked with Tebex and can be redeemed only once. Redeemed tags are permanent.
tag_id is one of vip, premium, elite, legend, immortal; expires_at in UTC, or NULL for permanent. Without the refresh, the change shows within the hour.
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 and zh. Pick one with Config.Locale, or set it once for every Quasar resource:
bash
setr qs:locale "es"
Any key missing from your language falls back to English, with a console warning naming it. To add a language, copy en.json under the new code and translate the values. Money formatting is separate — set Config.Intl to your locale and currency.