A practical guide to how to add a garbage job to your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.
Overview
This guide shows how to add a robust, secure garbage job to a QBCore FiveM server. It covers architecture, data modeling, server-side validation, client interactions, implementation choices (QBCore/ESX/ox_lib), testing, deployment and ongoing maintenance. The example emphasizes server authority: never trust client input. For designers using companion tooling or analytics, consider integrating external workflows such as the Stellar AI platform via https://trystellarai.com/app to prototype routes or asset placement, but keep runtime validation on your server.
Goals and requirements
- Create a job that players can start from a job center or phone.
- Provide route generation and a garbage truck vehicle with persistent job state stored server-side.
- Validate every collection/payout on the server and write reliable database updates.
- Support ox_lib / qb-target / qb-menu integrations for modern UI and targeting.
- Be testable locally and deployable with clear migration and maintenance steps.
High-level architecture
Design the system so the server is authoritative for job state, money, and inventory changes. The client should only render UI, play sounds/animations, and send intent (e.g., "I attempted to pick up bag id 123"). The server verifies the request, checks cooldowns, route ownership, and item counts, then updates the database and emits confirmation events back to the client.
Components:
- Server module: job registration, route manager, state persistence, payout logic.
- Database: tables for player job progress, truck assignments, route definitions, payouts.
- Client module: UI, marker/blip rendering, interaction handlers, animations.
- Optional integrations: ox_lib for menus, qb-target for interactions, oxmysql/ghmattimysql for DB.
Data model and persistence
Keep the model minimal but complete. Example tables/objects:
- garbage_routes (route_id, route_points JSON, payout_base)
- garbage_jobs (player_id, job_active boolean, route_id, progress JSON, truck_plate, last_update)
- garbage_logs (log_id, player_id, action, value, timestamp)
Store route progress and bag ownership server-side as a small JSON blob to avoid inconsistent client state. Always update through single atomic DB operations where possible (use transactions or a single UPDATE statement). For oxmysql you can use execute with parameterized queries; for ghmattimysql use the execute/async helpers your server provides.
Server-side implementation (QBCore example)
Key principles:
- Validate player job status, proximity to a bag, and truck assignment before accepting a collection request.
- Rate-limit collection calls and log suspicious behavior.
- Perform all money and item adjustments on the server and persist them immediately.
-- server/main.lua (simplified)
local QBCore = exports['qb-core']:GetCoreObject()
-- Secure handler: client requests to collect a bag
RegisterNetEvent('garbage:server:collectBag', function(bagId)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Fetch job state from DB or in-memory cache
local jobState = GetPlayerGarbageJob(Player.PlayerData.citizenid)
if not jobState or not jobState.job_active then
-- reject - player is not on the job
TriggerClientEvent('garbage:client:collectRejected', src, 'not_on_job')
return
end
-- Server-side proximity and legitimacy check:
if not ValidateBagOwnership(jobState, bagId) then
-- potential cheat attempt: do not trust client claims
LogSuspicious(src, 'invalid_bag_collect', bagId)
return
end
-- Calculate payout securely on server
local payout = CalculatePayoutForBag(jobState.route_id)
-- Update DB atomically, increment progress, and give payout
if UpdateJobProgressAndPayout(Player.PlayerData.citizenid, bagId, payout) then
Player.Functions.AddMoney('cash', payout, 'garbage-collection')
TriggerClientEvent('garbage:client:collectSuccess', src, bagId, payout)
else
TriggerClientEvent('garbage:client:collectRejected', src, 'db_fail')
end
end)
Server-side DB update pattern
Use parameterized SQL queries and wrap multi-statement changes in a transaction when possible. If using oxmysql, use exports.oxmysql:execute and handle errors. Always handle DB failures gracefully and inform the client of the server-side state.
Client-side implementation
The client should:
- Display route: blips and markers based on server-sent route points.
- Send intention events to the server (e.g., TriggerServerEvent('garbage:server:collectBag', bagId)).
- Listen for server confirmation events and update local state/UI only after confirmation.
Never award money or items on the client. Show temporary animations and "pending" UI if you want instant feedback, but mark them as tentative until the server confirms.
-- client/main.lua (simplified)
RegisterNetEvent('garbage:client:updateRoute', function(routeData)
-- draw blips & markers; do NOT trust client-only route as authority
RenderRoute(routeData)
end)
-- player interacts with bag
function OnInteractWithBag(bagId)
-- play pickup animation, then request server authorization
TriggerServerEvent('garbage:server:collectBag', bagId)
end
RegisterNetEvent('garbage:client:collectSuccess', function(bagId, payout)
-- show UI and update local progress
ShowPayoutNotification(payout)
end)
Integration choices: ox_lib, qb-target, ESX considerations
Modern FiveM setups often use ox_lib for menus and utilities, and qb-target for interaction targeting. Use these to present a clean player experience, but maintain the same server-authoritative flow.
- ox_lib: use its menus and progress bars for non-authoritative UI. All state changes must still originate from server validation.
- qb-target / ox_target: use target zones to offer "Collect" interactions. Trigger server events only on interaction confirmation.
- ESX servers: replace QBCore function calls with ESX equivalents. Keep server validation patterns identical.
Security and anti-cheat considerations
Never trust the client. Specific measures:
- Validate position on the server where possible. If you must rely on client-provided coordinates, verify these against the server’s expected route points and a reasonable distance threshold.
- Rate limit critical events per player and per truck to avoid spamming exploits.
- Log and review suspicious patterns: many different bag IDs collected in rapid succession, large payouts without DB updates, or requests from players without an active job.
- Do not accept client-side inventory changes; always check with the canonical server inventory.
Testing and debugging
Test locally before deploying. Recommended steps:
- Run a local server with minimal resources and a test database snapshot.
- Simulate normal job flows and edge cases: disconnect mid-route, DB failure during payout, duplicate bag collects.
- Enable verbose server logs for job events and DB queries when debugging, then reduce verbosity in production.
- Test third-party integration points (oxmysql, ghmattimysql) by intentionally failing the DB to see fallback behavior.
Use txAdmin or your preferred FiveM manager to monitor resource CPU and script errors during load testing. For behavior-driven tests, write small scripts that simulate many clients collecting on the same route to ensure race conditions are handled.
Deployment and maintenance
Deploy with migrations and rollback capability. Checklist before pushing updates:
| Step | Action | Who/When |
|---|---|---|
| DB Migration | Apply new table/schema changes using versioned migration scripts | DB Admin / Deploy |
| Backup | Create a backup snapshot of production DB | Ops / Before deploy |
| Config | Verify oxmysql/ghmattimysql connection strings and credentials | Dev/Ops |
| Smoke Test | Start server in staging and run the job end-to-end | QA |
| Monitor | Enable logs and quick rollback if anomalies detected | Ops / 24h after deploy |
Maintain: rotate logs, review payout economics periodically, and watch for new exploit patterns. Keep libraries (QBCore, ox_lib, qb-target) up to date and read their release notes for breaking changes.
Roblox notes (Luau, if recreating similar job on Roblox)
Roblox servers must also be authoritative. Use RemoteEvents only to send intents from client to server; all validation, awarding of in-game currency and DataStore writes must happen server-side. Patterns to follow:
- Use RemoteEvents to request actions, but validate player position/time/cooldowns on the server.
- When using DataStore, always use pcall() to handle service outages and implement exponential backoff or fallback queues to avoid data loss.
- Test DataStore failure modes in a controlled environment: queue saves locally in memory and drain the queue once DataStore recovers.
- Use ServerScriptService for authoritative logic and avoid trusting any client modifications.
Practical checklist
| Item | Pass/Fail | Notes |
|---|---|---|
| Server validates every collection | __ | Check event handlers and logs |
| DB updates are atomic | __ | Test concurrent collects |
| Client only renders UI | __ | Ensure no client money adds |
| Rate limits present | __ | Throttle high-frequency calls |
| Backup and migration scripts ready | __ | Versioned SQL scripts |
| Monitoring and alerts configured | __ | Log suspicious patterns |
Performance considerations
Large servers require efficient event handling and minimal DB churn. Suggestions:
- Cache job state in memory and flush to DB periodically or on important transitions rather than on every minor action; still persist critical events (payouts) immediately.
- Batch non-critical writes and use background workers to process logs or analytics.
- Avoid sending large route payloads on every tick — push route once and send incremental updates only when required.
Further reading and tools
For route prototyping or job analytics, teams sometimes leverage AI-assisted workflows during development. See the Stellar AI platform for research and experimentation: https://trystellarai.com/app. For blog-style writeups and community best practices, see https://trystellarai.com/blog.