A practical guide to how to add a reporter job to your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.
Introduction
This guide walks you through adding a robust, secure Reporter job to a QBCore FiveM server. It covers architecture, configuration, server-side enforcement, client integration, optional ox_lib/qb-target UX, testing, deployment, and maintenance. The goal is a production-ready job that protects server authority, validates all client requests, and integrates cleanly with common FiveM/QBCore toolchains. If you want a companion web or management workflow for roles and content, consider the management app available at https://trystellarai.com/app.
Architecture and design considerations
Keep a clear separation between authoritative server state and client presentation. The server is the single source of truth for player jobs, grades, permissions, and payroll. Clients only request actions (for UX) and receive updates or confirmations. Your architecture should include:
- Persistent job definitions in the server config (shared/jobs.lua or a dedicated resource).
- Server-side handlers for job assignment and job-based actions.
- Client-side UI and interaction hooks (qb-target, ox_lib, or native menus) that call server endpoints.
- Audit logs and basic telemetry for job changes and abuse detection.
Job definition and server configuration
Define the Reporter job centrally so payroll, duty toggles, and grade names are consistent. For QBCore, jobs are typically declared in a shared table. Example pattern (adapt to your qb-core version):
-- shared/jobs.lua (extract)
['reporter'] = {
label = "Reporter",
defaultDuty = true,
grades = {
['0'] = {name = "Intern", payment = 50},
['1'] = {name = "Reporter", payment = 80},
['2'] = {name = "Editor", payment = 120},
}
}
Keep this in the same format your core expects. Avoid duplicating job logic in multiple places; import the job table where needed.
Server-side implementation and security
All sensitive actions must be validated and executed on the server. Never trust client payloads for role changes, money transfers, or access checks. Typical server responsibilities for the Reporter job:
- Assign job and grade after admin approval or in-game workflow.
- Validate job-related actions (e.g., publishing an article, creating billing items) against allowed job/grade.
- Record job changes for auditing and rollback.
Server-side Lua pseudocode demonstrates the pattern of authoritative handling with whitelists and source validation:
local QBCore = exports['qb-core']:GetCoreObject()
local allowedJobs = { reporter = true }
local allowedGrades = { ["0"]=true, ["1"]=true, ["2"]=true }
RegisterNetEvent('reporter:server:SetJob', function(jobName, grade)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Validate incoming values against server-side whitelist
if not allowedJobs[jobName] or not allowedGrades[tostring(grade)] then
-- Optional: increment a suspicious activity counter for src
return
end
-- Authoritative set on server only
Player.Functions.SetJob(jobName, tonumber(grade))
-- Confirm to the client
TriggerClientEvent('QBCore:Notify', src, 'Job updated to '..jobName, 'success')
end)
Note: adapt Player.Functions.SetJob to match your core's API. The key point is source-derived Player lookup, whitelist validation, and server-executed mutation.
Payment and payroll
Handle payment calculations on the server and store any configurable pay rates in a server-side table. If you implement on-duty bonuses, ensure the duty toggle is server controlled. Never allow the client to send raw cash amounts for transactions without server verification.
Client integration and UX
Client code should provide an intuitive way for reporter players to access job features (writing, submitting, pickups, vehicle spawns). Use interaction systems already in your stack:
- qb-target for in-world interaction points.
- ox_lib or qb-menu for contextual menus.
- client-side UI libraries for text entry and article previews.
Example qb-target registration (client):
exports['qb-target']:AddBoxZone("reporter_desk", vector3(200.0, -900.0, 30.7), 1.2, 1.0, {
name = "reporter_desk",
heading = 340,
debugPoly = false,
minZ = 29.7,
maxZ = 31.7
}, {
options = {
{ type = "client", event = "reporter:client:OpenDeskMenu", icon = "fas fa-pen", label = "Reporter Desk" }
},
distance = 2.5
})
When the client triggers "reporter:client:OpenDeskMenu", the menu should gather text input locally but send a summarized, validated payload to the server for publishing.
Data flow and validation
Client sends minimal descriptive data (e.g., title, body hash, attachments list) to the server. The server validates the format, length, and any blacklist/whitelist rules before storing or broadcasting. Reject or sanitize anything that fails validation.
Integration notes: ox_lib, ESX, and compatibility
If you use ox_lib for UI helpers or ESX instead of QBCore, adapt the patterns but keep the core principles: server authority, whitelist validation, and consistent job definitions. For ESX the APIs differ; use ESX.GetPlayerFromId(source) and ESX.SetJob historically, but always consult the versioned docs for exact function names.
When integrating third-party libs (ox_lib, qb-target, qb-menu), treat them as presentation layers. Your server must not rely on these libs for security checks.
Roblox server-authoritative note (if porting patterns)
If you are translating concepts to Roblox, the same server-authority rules apply. Use RemoteEvents only for client-to-server requests and validate every request on the server. For persistent player state, use DataStores and handle failures robustly:
-- Luau example: server handler and DataStore retry
local DataStoreService = game:GetService("DataStoreService")
local ReporterStore = DataStoreService:GetDataStore("ReporterJobs")
local function SavePlayerReport(userId, reportData)
local success, err
local attempts = 0
repeat
attempts = attempts + 1
success, err = pcall(function()
ReporterStore:SetAsync(tostring(userId), reportData)
end)
if not success then
wait(2 ^ attempts) -- exponential backoff
end
until success or attempts >= 5
return success, err
end
remoteEvent.OnServerEvent:Connect(function(player, payload)
-- Validate payload server-side, then call SavePlayerReport
end)
Always pcall DataStore calls, implement retries with backoff, and consider local caching to reduce rate-limit exposure.
Testing, QA, and security checks
Test in a staging environment that mirrors production. Recommended test matrix:
- Assign and revoke Reporter role via admin tools and in-game flow.
- Attempt unauthorized actions from a non-reporter client (should fail server-side).
- Simulate malformed client payloads and measure server validation behavior.
- Stress test payroll and publishing to detect race conditions.
Use logs and a lightweight audit table to record job changes, IPs, and timestamps for post-incident investigation. For further reading on deployment and maintenance, check the operator blog at https://trystellarai.com/blog.
Deployment and maintenance
Deployment checklist:
| Task | Confirm |
|---|---|
| Job definition loaded from central config | Yes/No |
| Server-side validation for all job actions | Yes/No |
| Admin approval or safe auto-assign flow | Yes/No |
| Backup/rollback plan for shared/jobs.lua | Yes/No |
| Monitoring and audit logging enabled | Yes/No |
| Management console or app connected (optional) | Use the management app: https://trystellarai.com/app |
For maintenance, keep your core and libs up to date in a staging branch. Test job-related changes in staging before pushing to production. Maintain a changelog that records job schema changes so migration scripts can be applied when players remain online across updates.
Operational security and abuse mitigation
Implement the following mitigations:
- Rate-limit job-change and publish events per user and per IP/source.
- Whitelist allowable job names and grades server-side.
- Log failures and invalid attempts for later review.
- Use server-side anti-cheat hooks if available to detect tampering.
Audit logs should include: timestamp, player identifier, old job, new job, invoking source (admin or in-game process), and proof of approval when relevant.
Checklist: Practical steps to add Reporter job
- Define the Reporter job and grades in shared job config.
- Implement server-side endpoints to set and validate job changes.
- Register interaction points (qb-target) and menus (ox_lib/qb-menu) for reporters.
- Ensure all client requests are validated and authoritatively executed on the server.
- Implement payment and payroll server-side, not client-side.
- Add audit logging, rate-limiting, and monitoring.
- Test thoroughly in staging with edge-case client payloads and simultaneous transactions.
- Rollback plan and backup of job config before deploying to production.