A practical guide to how to add a farming job to your qbcore fivem server, with implementation decisions, validation steps, and security considerations for a production-minded project.
Overview
This guide walks through adding a robust, secure farming job to a QBCore-based FiveM server. It covers architecture, data flow, security and server-side validation, implementation choices (QBCore, ox_lib, oxmysql), testing, and deployment/maintenance. The goal is a production-ready job that resists client-side tampering, scales to multiple players, and integrates cleanly with your economy and item systems.
High-level architecture
A farming job is a coordinated set of server and client responsibilities:
- Client: UI, targeting zones, visual feedback (animations, progress bars). Never trust client assertions about success, quantities, or positions.
- Server: authoritative validation, inventory and money transactions, cooldowns and anti-duplication, requests to persistent storage (SQL or datastore).
- Database: item definitions, farmable locations, job progress if persistent across sessions.
Typical tools and libraries: QBCore for player and job frameworks, ox_lib for UI/menus/notifications, and oxmysql (or ghmattimysql) for SQL persistence. Keep the authoritative logic on the server and treat clients as untrusted renderers and input forwarders.
Job flow and state machine
A concise state machine reduces edge cases. Example states for a single farm interaction:
- Player approaches a field / zone (client shows prompt).
- Player initiates action (client sends server event request to start harvesting).
- Server validates player: proximity to the defined zone, job permissions (if job-limited), cooldowns, and inventory space.
- If valid, server reserves the farm node, returns a token/acknowledgement to the client, and optionally triggers client progress animation.
- Client shows progress; on completion it notifies server to claim reward. Server re-validates server-side timer and reservation, then awards items/money and updates DB.
- Server releases node reservation and records cooldowns or state to prevent farming duplication.
Server-side implementation (QBCore patterns)
Keep all critical state and rewards on the server. Use QBCore.Functions.GetPlayer to access a player's inventory and money APIs. Use server events and callbacks only as a request mechanism—the server must re-check everything before mutating state.
Example server-side pseudo-code (QBCore v2+ patterns):
local QBCore = exports['qb-core']:GetCoreObject()
local reservedNodes = {} -- track active reservations by nodeId
QBCore.Functions.CreateCallback('farm:server:canStart', function(source, cb, nodeId)
local src = source
local player = QBCore.Functions.GetPlayer(src)
if not player then return cb(false, "Player not found") end
-- Validate node exists, is not reserved, and player is near node (server geometry check)
local node = MyFarmNodes[nodeId]
if not node or reservedNodes[nodeId] then
return cb(false, "Node unavailable")
end
-- Optionally check player's job, inventory space, cooldowns
if player.PlayerData.job.name == 'police' then
return cb(false, "Not allowed for police")
end
-- Reserve node and return success
reservedNodes[nodeId] = {by = src, ts = os.time()}
return cb(true)
end)
RegisterNetEvent('farm:server:complete', function(nodeId)
local src = source
local player = QBCore.Functions.GetPlayer(src)
if not player then return end
local reservation = reservedNodes[nodeId]
if not reservation or reservation.by ~= src then
return TriggerClientEvent('QBCore:Notify', src, "Invalid completion", "error")
end
-- Award items server-side
player.Functions.AddItem('potato', 3)
-- Optionally add money
player.Functions.AddMoney('bank', 25)
-- Release reservation and log
reservedNodes[nodeId] = nil
end)
Notes on server geometry validation
Do not trust client-reported coordinates without rechecking. For low-latency, compare distances between the player's last-known server position (from a recent authenticated heartbeat) and the node coordinates. If you need precise checks, use server-side raycasts or validate timestamps to avoid replayed completions.
Database design and items
Store farm node definitions (coords, type, respawn time) in a persistent table. For farm progress that must persist across reconnections (like long grow times), persist node states with timestamps.
| Table / Key | Purpose | Fields / Example |
|---|---|---|
| farm_nodes | Static node definitions | id, x, y, z, type, respawn_seconds |
| farm_state | Dynamic node state | node_id, status (available/reserved/growing), reserved_by, reserved_until |
| player_items | Handled by QBCore inventory | Use Player.Functions.AddItem / RemoveItem |
Use transactions or atomic SQL updates to prevent race conditions when multiple players try to harvest the same node simultaneously. With oxmysql, prefer parameterized queries and check affectedRows to ensure updates happened.
Client-side: UI and safe communication
The client should only display prompts and send requests. Example pattern:
- Client detects proximity to farm zone and shows an interaction prompt (qb-target / qtarget / ox_target).
- When player presses interact, client calls a server callback (QBCore.Functions.TriggerCallback) to ask for permission.
- If server grants permission, client runs animations and shows progress. When progress completes, client triggers a server event to finalize.
- If progress is interrupted (player moved or died), client notifies server to cancel; server should verify and free reservations.
-- client example
QBCore.Functions.TriggerCallback('farm:server:canStart', function(allowed)
if allowed then
-- play animation, show progress bar
TriggerServerEvent('farm:server:complete', nodeId)
else
QBCore.Functions.Notify('Cannot start farming', 'error')
end
end, nodeId)
Never let the client directly add items or write to server storage. All adds/removes must happen in server-side code.
Security, validation, and anti-exploit strategies
Key rules to enforce:
- Server is authoritative: All inventory and money mutations occur on the server.
- Validate proximity & timing: Re-check the player's position and the node state on every sensitive request.
- Reserve nodes: Use server-side reservations with expirations to avoid duplicate claims.
- Cooldowns & rate-limits: Enforce per-player cooldowns and global limits for farm yields to prevent automated farming.
- Anti-duplication checks: Verify that awarding items is based on server-confirmed events and check inventory deltas when feasible.
- Log suspicious behavior: Store server-side logs for failed validations and repeated rapid requests for later review.
Consider integrating your server logs with an external monitoring service or alerting system to notify you of spikes or unusual patterns.
Testing and debugging
Test the job in stages:
- Unit test server callbacks with simulated sources to validate logic without client overhead.
- Local multiplayer tests (multiple clients) to attempt race conditions on the same node.
- Edge-case tests: disconnects during active harvest, server restart during a reserved node, and inventory full cases.
- Stress tests on production-like hardware to observe database contention and lock behavior.
Useful debug tools and patterns:
- Verbose server logs guarded by a debug flag.
- Temporary testing endpoints that simulate nodes and responses.
- Database query logging for slow queries and failed updates.
Deployment and maintenance
Deploy with migrations for database changes. Use semantic versioning for your job resource and keep migration scripts with the resource. Back up the farm_state table before schema upgrades. Monitor server CPU and DB connections for contention during peak hours.
If you want to prototype or visualize your job flow with a third-party tool, check out this resource hub: Stellar AI App. For production task and resource management workflows, consider the same tool to track changes: Stellar AI App.
Roblox considerations (if porting concepts or building a Roblox equivalent)
If you also build a farming activity in Roblox, preserve similar server-authority principles. Use RemoteEvents only as input channels from client to server. Always validate player positions and inventory on the server. For persistence, use DataStore with robust pcall handling and retries. Example patterns:
- Wrap DataStore calls in pcall and implement exponential backoff for transient failures.
- Use server-side reservations for nodes, stored in Memory or a persistent DataStore for long grow cycles.
- Never trust client-reported completion; the server should compute eligibility from its own state and timestamps.
Practical checklist
| Task | Files / Config | Validation |
|---|---|---|
| Create farm node table | sql/farm_nodes.sql | Run migration, verify SELECT returns nodes |
| Implement server callbacks | server/farm_server.lua | unit tests for race conditions and reserve logic |
| Client prompt and progress | client/farm_client.lua | Simulate interruptions and verify server cancels |
| Inventory and reward handling | server/rewards.lua | Confirm items added only via server path |
| Monitoring and logs | server/logging.lua | Trigger alerts on suspicious request rates |
Maintenance tips
- Automate DB backups and schema migrations as part of CI/CD.
- Rotate and archive old farm_state rows to keep performance optimal.
- Version your job resource and keep change logs so admins can rollback if needed.
- Run scheduled integrity checks to find orphaned reservations and clear them automatically after expiration.
Further reading
For development workflow and community articles, see the related writeups and tutorials on the official blog: Stellar AI Blog.