FiveM & QBCore · Practical guide

How the QBCore Job System Works — Complete Guide

Understanding the QBCore job system is essential for any server owner. This guide covers how jobs work, how to add new ones, set grades and configure pay.

Stellar AI · Updated 8 September 2026 · 6 min read

A practical guide to how the qbcore job system works — complete guide, with implementation decisions, validation steps, and security considerations for a production-minded project.

Overview

This guide explains how a QBCore job system works in a FiveM server, covering architecture, implementation choices, security and server validation, testing, and maintenance. It also highlights differences and important considerations for Roblox implementations (Luau) where relevant. The goal is practical: show patterns you can copy, validate, and maintain safely.

Core architecture of a QBCore job system

A robust job system separates responsibilities across layers. Typical components include:

  • Job definitions (shared data): names, grades, permissions, pay scales, and available tasks.
  • Server-side authority: job assignment, payout calculation, inventory changes, and database persistence.
  • Client-side UI: markers, menus, task progression visuals — strictly presentation only.
  • Persistence layer: MySQL or an ORM, with transactions for critical changes and backups for job-related payouts.
  • Integration layer: optional exports to ox_lib, ESX compatibility shims, and admin tools.

Design principle: the server is authoritative. Never assume a client honestly reports job progress, money, or inventory changes.

QBCore components and job definitions

In QBCore, define jobs in a shared config file so both client and server can reference the same structure. Keep sensitive values (pay formula, limits) on the server only. Example definition elements:

  • job name (string)
  • grades (array: permissions, salary multiplier)
  • actions (table: task identifiers mapped to server-side handlers)
  • locations (coords used by clients for UI only)
-- shared/jobs.lua (example)
Jobs = {
  garbage = {
    label = "Garbage Collector",
    grades = { ['0'] = {pay = 50}, ['1'] = {pay = 75} },
    tasks = { "collect_bin", "deliver_trash" },
    locations = { start = vector3(10.0, 6500.0, 31.0) }
  },
  -- other jobs
}

Server-side validation & security

Security is the most important part of a job system. Treat any client-sent event as untrusted input. The server must validate:

  • Player identity and job name using QBCore.Functions.GetPlayer(source)
  • That the player's job grade permits the requested action
  • No modification of payout values from the client — compute payouts server-side
  • Rate-limit high-value events and use anti-spam logic
  • Use prepared statements / parameterized queries for DB writes

Example server pattern (QBCore):

-- server/main.lua
local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('qb-jobs:server:completeTask', function(taskId)
  local src = source
  if not src then return end
  local Player = QBCore.Functions.GetPlayer(src)
  if not Player then return end

  local jobName = Player.PlayerData.job.name
  -- validate job and task ownership on server
  if jobName ~= 'garbage' then return end

  -- map taskId to server-side reward & checks
  local task = Jobs.garbage.tasks[taskId]
  if not task then return end

  local reward = CalculateServerReward(jobName, Player.PlayerData.job.grade.level, taskId)
  if reward <= 0 then return end

  -- apply money server-side and persist transaction
  Player.Functions.AddMoney('cash', reward, "job-complete-" .. taskId)
end)

Key points: perform all payout calculation and permission checks on the server. Log suspicious behavior and disconnect or flag for review instead of rewarding unexpected values.

Client-side behavior and UI

Client scripts should be thin: spawn markers, play animations, and send minimal events to the server indicating attempts or confirmations. Never send reward values, and avoid sending unobfuscated task progress that could be modified. When a client submits "task complete", the server should verify all aspects before any state change.

Use client-side movement checks and simple visual validation for UX, but do not treat these as authoritative.

Integration choices: ox_lib, ESX, and interoperability

When integrating with ox_lib or providing an ESX compatibility layer, keep the QBCore server logic central and expose safe exports only. For example:

  • Provide an export for reading job definitions (read-only)
  • Expose server callbacks for admin tools that require permission checks
  • Avoid exposing any function that directly mints currency on the client request — require server callbacks that perform permission checks

If you must support ESX players, translate data at the server boundary and maintain one canonical job state; do not duplicate logic across frameworks.

Implementation choices: database, permissions, grades

Decisions you will need to make:

  • DB schema: store PlayerData.job.name and grade, job logs, and payout history. Use transactions when modifying both balance and job progress.
  • Grades & permissions: implement a simple RBAC model — grades map to permission flags. Do not assume numeric grade implies permissions; check flags by name.
  • Task definitions: identify tasks by server-side IDs rather than client names; allow server to change task parameters without client updates.

Maintain migrations for job schema changes to ensure upgrades don't break running servers.

Testing, debugging, and anti-cheat testing

Testing should include automated unit tests for server-side logic and manual scenario tests for client/server interactions.

  • Simulate malicious clients by sending invalid events and verify the server rejects them.
  • Unit test payout calculations and DB transactions.
  • Use sandbox environments to test migrations and new job definitions before deploying to production.

Log minimal, structured information on failures (error codes, player id, event name) and aggregate logs for pattern detection. If you use an external monitoring dashboard consider low-latency hooks; for example, a development tool or a hosted app can help visualize job activity — try the app here: Stellar AI App.

Roblox considerations: RemoteEvents and DataStore robustness

When porting job concepts to Roblox:

  • RemoteEvents are the only acceptable client→server path. The first parameter for OnServerEvent is always the player — use it to validate identity.
  • Never accept client-calculated rewards. Calculate on the server and then use RemoteEvent:FireClient to confirm.
  • DataStore failures are common; wrap writes in pcall, implement exponential backoff, batched writes, and queuing for critical transactions. Avoid losing money changes on failure by keeping an in-memory queue with write retries.
  • Rate-limit RemoteEvents per player and monitor for abnormal frequency.
-- Roblox server-side pattern (Luau)
local Remote = game.ReplicatedStorage:WaitForChild("CompleteTask")
Remote.OnServerEvent:Connect(function(player, taskId)
  local playerJob = GetPlayerJob(player) -- server-side
  if playerJob ~= "Delivery" then return end
  local reward = CalculateRewardForTask(taskId, playerJob)
  -- persist with DataStore, wrapped in pcall
  local success, err = pcall(function()
    -- DataStoreSet implementation
  end)
  if not success then
    -- queue retry or inform player via RemoteEvent
  else
    GivePlayerMoney(player, reward)
  end
end)

Deployment and maintenance

Deployment checklist:

  • Use a CI pipeline to deploy server scripts and DB migrations.
  • Keep configuration values for pay scales and limits in a versioned config file.
  • Have a rollback plan: a migration that reverts job schema changes quickly.
  • Monitor error logs, suspicious behavior alerts, and latency for DB writes.

For centralized dashboards and operational workflows, you can integrate server logs or metrics into an external service. If you need a hosted monitoring and deployment assistant, the following app can be useful: Stellar AI App. For editorial updates and deeper articles on server architecture, see: Stellar AI Blog.

Practical checklist

Item Why it matters Action
Server-side job validation Prevents unauthorized rewards Verify job name and grade with QBCore.Functions.GetPlayer before actions
Server-calculated payouts Clients can be manipulated Map taskId → server payout; ignore client-sent amounts
DB transactions Atomicity for money & progress changes Use transactional writes or rollback on failure
Rate limiting Prevents spam and automated abuse Track last event timestamps per player and block rapid repeats
Logging & monitoring Detect exploits Log structured events; aggregate with dashboards
Roblox DataStore retries Network errors are common Use pcall, retry with backoff, and queue unsaved changes

Common pitfalls and how to avoid them

  • Trusting client payloads: Never perform financial operations based on client values. The server should compute and execute.
  • Lack of auditing: Not logging job payouts or suspicious events makes it impossible to post-mortem exploits. Keep concise logs for important transactions.
  • Mixing frameworks without boundary: If you support both QBCore and ESX, keep a single authoritative module for job logic and only adapt interfaces per framework.
  • Ignoring DataStore retries (Roblox): Always handle failure paths; do not assume a write always succeeds.

Build your next system with Stellar AI

Describe one feature, get organized project files, then bring back your errors to keep improving. Start free with no card required. Test generated code in a private development environment before release.

Create your first script free →