How Integrations Work#
Nothing inside the Job Center core knows which framework or target you run. Every framework- or target-specific call sits in a small, unencrypted bridge file under client/custom/ and server/custom/, and only the one that matches your server runs.
Layer | Supported | Without it |
|---|---|---|
Framework |
| Standalone test mode. |
Target |
| A proximity prompt and |
Prompt |
| Floating text drawn by the game. |
Boss tablet | Your framework's usable items, in any inventory that supports them | Use |
Phone |
| The job center stays at the clerk. |
Logs | A Discord webhook | Nothing is logged. |
Every optional integration degrades silently. A missing resource makes its part fall back — nothing errors and nothing needs switching off.
Frameworks#
The framework decides who a player is, which job and grade they hold, how a job is set on an instant hire and how notifications look:
Framework | A chat belongs to | Name shown | Notifications |
|---|---|---|---|
ESX | The character's |
|
|
QBCore |
| First and last name |
|
Qbox |
| First and last name |
|
Standalone | The server id |
| The game's own notification |
Because a chat follows the character, each character on a multicharacter server has its own applications. Replies that arrived while a player was away are announced when the framework reports the character loaded (esx:playerLoaded or QBCore:Client:OnPlayerLoaded, which Qbox fires too).
Standalone mode#
Without a framework every player is unemployed at grade 0, an instant hire changes the job only inside Job Center, and there is no usable item. It exists to try the interface, not to run a city.
Targets and the Prompt#
With ox_target or qb-target the clerk carries one option: a briefcase icon and the text of Config.JobCenterPed.label.
Without a target, standing within two metres of the clerk shows a prompt and E opens the window. If Text UI (qs-textui) is started, the prompt is drawn by it; otherwise the game draws [E] Open Job Center above the clerk.
To keep the prompt while a target runs on your server — for example to use qs-textui everywhere — switch the target off for Job Center only:
-- shared/config.lua
Config.UseTarget = false -- the clerk uses the prompt even when a target is runningNotifications#
Every message shown over the game — You have been hired as …, a new reply, a waypoint set — goes through one function, showNotification, in your framework's bridge. To send them to another notification resource, change that function. ox_lib is already loaded, so its notifications need nothing else:
-- client/custom/framework/qbcore.lua (or esx.lua, qbox.lua)
function showNotification(message, type, duration)
lib.notify({
description = message,
type = (type == 'primary') and 'inform' or type, -- 'success' · 'error' · 'primary'
duration = duration or 5000,
})
endDiscord Logs#
Four kinds of event can be posted to a Discord channel as colour-coded embeds, each with its own switch:
Event | Posted when | Colour | Ships |
|---|---|---|---|
| An applicant sends a message — every message, not only the first. Player, server id, job and company, and the message. | Blue | On |
| Someone takes an instant job. Player, job, company, salary and location. | Green | On |
| A boss answers an applicant. Boss, job, applicant and the message. | Orange | On |
| A boss closes an application. Who closed it, the job and the applicant. | Red | Off |
-- shared/config.lua
Config.DiscordWebhook = {
enabled = true,
url = 'https://discord.com/api/webhooks/...',
botName = 'Job Center',
botAvatar = '', -- an image URL, or empty for Discord's default
events = {
jobApplication = true,
jobHired = true,
bossReply = true,
chatDeleted = false,
},
}Nothing is sent until enabled = true and url is filled in. Messages longer than 200 characters are cut in the embed, and every embed carries the date and time in its footer.
The Careers HQ Phone App#
Careers HQ is the job center on Quasar Smartphone: the same listings and categories, applying, the applicant chat with push notifications, and a recruiter view for bosses. It is a separate resource, phone-careershq, that reads Job Center through server/custom/sv_careershq.lua — so an application started on the phone and one started at the clerk are the same conversation.
ensure ox_lib
ensure qs-smartphone
ensure qs-jobcenter
ensure phone-careershqThe bridge needs nothing beyond ox_lib, and without the app it simply waits. The phone's own apps and settings are covered in Smartphone → Integrations; the callbacks the bridge offers are in Commands and Events.
Overriding Detection#
Each layer is detected by looking for a started resource. To pin one by hand, replace the detection call in shared/config.lua:
-- Auto, the default
Config.Framework = checkDependencies(frameworks) or 'standalone'
Config.Target = checkDependencies(targets) or 'standalone'
-- Pinned
Config.Framework = 'qbcore' -- 'esx' · 'qbcore' · 'qbox' · 'standalone'
Config.Target = 'ox_target' -- 'ox_target' · 'qb-target' · 'standalone' (the prompt)This is worth doing when two candidates run at once — two targets, or a compatibility resource that answers to another framework's name — because then which one is found first is not defined.
Adding a Framework or Target#
Supporting something that is not on the list is one file per side plus one line of config:
Copy the closest bridge
Duplicate
client/custom/framework/esx.luaandserver/custom/framework/esx.lua— orclient/custom/targets/ox_target.luafor a target — under a new name. Change the guard on the first line to your own id:lua if not Config.Framework or Config.Framework ~= 'mycore' then return endFill in the functions
Bridge
Functions
Client framework
getPlayerData · triggerCallback · showNotification · getCitizenId · getPlayerName · getPlayerJob · getPlayerGrade · registerNetEvent · triggerServerEvent · registerCommand · registerNUICallbackServer framework
getPlayer · getPlayerByCitizenId · getPlayerData · getCitizenId · getPlayerName · getPlayerJob · getPlayerGrade · getPlayers · createCallback · createUseableItem · triggerClientEvent · registerServerEvent · getPlayerSource · setPlayerJobTarget
addTargetEntity(entity, options)Select it
Pin
Config.Framework = 'mycore'(orConfig.Target), or add your resource to the detection list at the top ofshared/config.lua—['my_core'] = 'mycore'inframeworks— and restart.
Languages#
28 translations ship in locales/: Arabic (ar), Bulgarian (bg), Catalan (ca), Czech (cs), Danish (da), German (de), Greek (el), English (en), Spanish (es), Persian (fa), French (fr), Hindi (hi), Hungarian (hu), Italian (it), Japanese (ja), Korean (ko), Dutch (nl), Norwegian (no), Polish (pl), Portuguese (pt), Romanian (ro), Russian (ru), Slovenian (sl), Swedish (sv), Thai (th), Turkish (tr), Simplified Chinese (zh-CN) and Traditional Chinese (zh-TW). Pick one with Config.Language; a code with no file falls back to English.
To add a language, copy locales/en.lua to a new file, change its first line to Locales["xx"] = {, translate the values — keep the keys and every %s and %d — and set Config.Language = 'xx'. Every file in locales/ is loaded on its own.
The locale files translate the interface and the notifications. Listings and categories are written in
shared/config.luaand show exactly as written there.



