Create a QBCore side job
This guide builds a delivery job without editing an existing gameplay resource. The client handles prompts and route state; the server validates job state and pays the player.
Define the job
Add the definition to qb-core/shared/jobs.lua and restart qb-core on a test server:
QBShared.Jobs.delivery = {
label = 'Delivery',
defaultDuty = true,
offDutyPay = false,
grades = {
['0'] = { name = 'Rookie', payment = 200 }
}
}Create the resource
Create resources/[local]/delivery-job/fxmanifest.lua:
fx_version 'cerulean'
game 'gta5'
lua54 'yes'
client_script 'client.lua'
server_script 'server.lua'
dependency 'qb-core'Add ensure delivery-job after qb-core in server.cfg.
Keep assignment and payouts on the server
Do not expose an event that lets any client choose a job or payout. Assign the job through an authorized admin/cityhall workflow. For the route, store issued work server-side and ignore any client-supplied amount:
local QBCore = exports['qb-core']:GetCoreObject()
local activeRoutes = {}
RegisterNetEvent('delivery:server:startRoute', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player or Player.PlayerData.job.name ~= 'delivery' then return end
if activeRoutes[src] then return end
activeRoutes[src] = { stopsRemaining = 3 }
TriggerClientEvent('delivery:client:routeStarted', src, 3)
end)
RegisterNetEvent('delivery:server:completeStop', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
local route = activeRoutes[src]
if not Player or Player.PlayerData.job.name ~= 'delivery' or not route then return end
route.stopsRemaining = route.stopsRemaining - 1
if route.stopsRemaining > 0 then return end
activeRoutes[src] = nil
Player.Functions.AddMoney('bank', 200, 'delivery-route')
end)
AddEventHandler('playerDropped', function()
activeRoutes[source] = nil
end)For production, also validate proximity, route order, cooldown, and entity ownership on the server.
Add the client interaction
Use qb-target, qb-menu, or a marker to request a route. The client should send an intent such as “start route” or “complete stop”, never a payout.
Test the resource
Restart qb-core and the new resource on a test server. Assign grade 0, reconnect, start and finish
one route, reject an invalid payout request, and confirm the resource stops without leaving targets
or entities behind.