🛡️ Safe Server Events - QBCore Guide for FiveM
Introduction
This tutorial turns 🛡️ Safe Server Events into a clean, developer-friendly guide for QBCore/FiveM. You will follow a step-by-step flow, copy the relevant code patterns, and learn the “why” behind the setup.
Requirements
- QBCore installed and running on a dev server
- Basic Lua knowledge and comfort reading FiveM patterns
- A test workflow for iterating safely (dev server, not production)
- Optional: a code editor with Lua/FiveM helpers (VS Code recommended)
Step-by-Step Guide
Step 1: Threat model
In this step, you will apply the threat model concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 2: Principles for secure events
In this step, you will apply the principles for secure events concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 3: Implementation example
In this step, you will apply the implementation example concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 4: Schema validation helpers
In this step, you will apply the schema validation helpers concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 5: Monitoring and response
In this step, you will apply the monitoring and response concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 6: Integration tips
In this step, you will apply the integration tips concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Step 7: Checklist
In this step, you will apply the checklist concept as a practical change: define the pieces, wire them together, then verify the behavior in your dev server.
Code Example
local QBCore = exports['qb-core']:GetCoreObject()
local allowedJobs = {
police = 2, -- requires grade 2+
ambulance = 3
}
local function hasPermission(player)
if not player then return false end
local job = player.PlayerData.job
local requiredGrade = allowedJobs[job.name]
return requiredGrade and job.grade.level >= requiredGrade
end
local function validatePayload(data)
if type(data) ~= 'table' then return false, 'Invalid payload type' end
if type(data.target) ~= 'string' or #data.target < 4 then
return false, 'Target citizen ID missing'
end
if type(data.reason) ~= 'string' or #data.reason > 120 then
return false, 'Reason must be under 120 characters'
end
return true
end
local function throttle(source)
local key = ('event:escort:%s'):format(source)
local now = os.time()
local last = GlobalState[key] or 0
if now - last < 3 then
return false, 'Slow down'
end
GlobalState[key] = now
return true
end
RegisterNetEvent('qb-escort:server:start', function(data)
local src = source
local player = QBCore.Functions.GetPlayer(src)
local ok, err = throttle(src)
if not ok then
QBCore.Functions.Notify(src, err, 'error')
return
end
if not hasPermission(player) then
DropPlayer(src, 'Unauthorized event usage detected')
return
end
local valid, message = validatePayload(data)
if not valid then
TriggerClientEvent('QBCore:Notify', src, message, 'error')
return
end
exports['qb-log']:CreateLog('security', 'Escort Event', 'green', (
'%s (%s) started escort on %s for %s'
):format(player.PlayerData.name, player.PlayerData.citizenid, data.target, data.reason))
TriggerClientEvent('qb-escort:client:start', src, data.target)
end)Tips & Best Practices
- Keep authority on the server: validate inputs before money/database operations.
- Start with one resource/module at a time, then refactor after you verify it works.
- Use callbacks for request/response flows and events for push/UX updates.
- When you run loops, avoid freezes: always yield with Wait() (client/server) and cache hot values.