How Integrations Work#
Text UI does not need a framework. It reads one for a single thing — the player's job, so a prompt can be shown to one job only. The framework is picked by looking at what is already running, and each one is handled by a small bridge file you can open and edit:
Resource started | Bridge | Job read from |
|---|---|---|
|
| The player data, kept current by |
|
| The player data, kept current by |
|
| The same, through Qbox's QBCore compatibility. |
None of them | — | Standalone. Every prompt works, as long as its |
On a standalone server, a prompt whose
jobis anything other than'all'raises an error instead of showing, because there is no job to compare with. Leavejobout, or set it to'all'.
ESX TextUI#
ESX scripts show their prompts with ESX.TextUI and hide them with ESX.HideUI, which normally go to esx_textui. Point those two functions at Text UI and every ESX script on the server uses it without being touched.
Find the two functions
Open
es_extended/client/functions.luaand look for:lua function ESX.TextUI(...) return IsResourceFound('esx_textui') and exports['esx_textui']:TextUI(...) end function ESX.HideUI() return IsResourceFound('esx_textui') and exports['esx_textui']:HideUI() endReplace them
lua function ESX.TextUI(message, _type) return exports['qs-textui']:displayTextUI(message) end function ESX.HideUI() return exports['qs-textui']:hideTextUI() endThe second value ESX passes is a colour type (
info,success,error), not a key, so it is left out and the badge showsConfig.DefaultKey. Codes such as~INPUT_CONTEXT~in older ESX messages are removed automatically.Restart the server
Restart the whole server rather than
es_extendedalone — every ESX resource holds its own copy of the shared object.
QBCore and Qbox DrawText#
QBCore scripts use exports['qb-core']:DrawText(text, position), ChangeText, HideText and KeyPressed. They live in qb-core/client/drawtext.lua. Replace the first three functions in that file, and leave keyPressed and the exports(...) lines at the bottom as they are:
-- qb-core/client/drawtext.lua
local positions = {
left = 'center-left',
right = 'center-right',
top = 'top-center'
}
local function hideText()
exports['qs-textui']:hideTextUI()
end
local function drawText(text, position)
exports['qs-textui']:displayTextUI(text, nil, positions[position])
end
local function changeText(text, position)
exports['qs-textui']:changeText(text)
endQBCore's positions are mapped to Text UI's, and a script that passes none gets Config.DefaultAlign. QBCore does not send a key, so the badge shows Config.DefaultKey.
QBCore position | Text UI position |
|---|---|
|
|
|
|
|
|
Qbox keeps the same DrawText, ChangeText and HideText exports for compatibility. Make the same change where qbx_core defines them — search its folder for exports('DrawText'.
Showing a Prompt to One Job#
Every 3D prompt and config area takes a job. Players with another job do not see it at all, and the job is read live — a player who changes job sees the prompt appear or disappear within a quarter of a second. For anything finer, such as a grade or a duty state, add a canInteract:
exports['qs-textui']:create3DTextUI('mechanic_tuning', {
coords = vector3(-211.5, -1324.6, 30.9),
displayDist = 8.0,
interactDist = 2.0,
enableKeyClick = true,
keyNum = 38,
key = 'E',
text = 'Tuning menu',
job = 'mechanic',
canInteract = function()
return IsPedInAnyVehicle(PlayerPedId(), false)
end,
triggerData = { triggerName = 'my-mechanic:client:openTuning', args = {} }
})Adding a Framework#
Supporting a framework that is not on the list takes one file and one line.
Write a bridge
Create
client/custom/framework/myframework.lua. It only has to return the player's job name and grade:lua if Config.Framework ~= 'myframework' then return end function GetJobName() local job = exports['my_framework']:GetPlayerJob() return job and job.name or 'unemployed' end function GetJobGrade() local job = exports['my_framework']:GetPlayerJob() return job and job.grade or 0 endReplace
my_frameworkandGetPlayerJobwith whatever your framework offers.GetJobNameis called often, so read a cached value rather than asking the server.Register it
In
shared/config.lua, add the framework's resource name to the detection list:lua local frameworks = { ['es_extended'] = 'esx', ['qb-core'] = 'qb', ['qbx_core'] = 'qb', ['my_framework'] = 'myframework' }Restart
Start your framework before
qs-textuiand restart the server.Config.Frameworknow resolves to'myframework'and your bridge is the one that loads.



