FiveM & QBCore · Practical guide

How to Add an Ambulance Job to Your QBCore FiveM Server

Step by step guide to adding a QBCore ambulance/EMS job to your FiveM server. Reviving players, hospital system and job menu explained.

Stellar AI · Updated 8 September 2026 · 6 min read

A practical guide to how to add an ambulance 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 ambulance job to a QBCore FiveM server. It covers architecture choices, server-authoritative patterns, integration points (vehicles, garage, revives, billing), testing, and deployment considerations. Examples use modern QBCore patterns and emphasize never trusting client input — the server must validate all actions. If you want a GUI or management tooling during development, try the app at https://trystellarai.com/app for workflow help and later check the blog for best practices at https://trystellarai.com/blog.

High-level architecture

Design the ambulance feature as a cohesive resource (for example qb-ambulance) with a clear separation of responsibilities:

  • Server authority: job registration, permissions, billing, inventory changes, database writes.
  • Client responsibility: animations, menus, display-only UI, local effects (not authoritative actions).
  • Shared definitions: job names, labels, configuration constants, and locales.

Keep the server side minimal and authoritative: clients send intents (e.g., "I want to revive player X") and the server confirms legitimacy, performs checks, and executes the action.

QBCore job registration and data model

QBCore stores jobs in shared configuration. You can register the ambulance role in your project's shared jobs file or as part of your resource. Typical steps:

  1. Add an entry to shared/jobs.lua (or your resource's config) with sensible permissions and labels.
  2. Create role-specific configuration: equipment, vehicles, revival cost, allowed items.
  3. Ensure database consistency: existing player job entries must match the new job name. If migrating, update your DB rows to the new job name prior to deploying.

Example snippet (conceptual):

-- shared/jobs.lua (illustrative)
jobs['ambulance'] = {
  label = "EMS",
  defaultDuty = false,
  grades = {
    ['0'] = { name = "Probationary", salary = 50 },
    ['1'] = { name = "Medic", salary = 100 },
    ['2'] = { name = "Doctor", salary = 150 }
  }
}

Server-side validation and security

Never trust client input. All client-initiated actions must be verified server-side before applying effects. Common attack vectors include falsified heal events, forged bills, and unauthorized vehicle spawns. Mitigations:

  • Validate the caller's job and grade on every server event.
  • Check proximity: confirm the target player is within an expected range on the server (send both player coords from server and compare).
  • Rate-limit critical actions (e.g., revives) and require cooldowns or resource consumption.
  • Log unusual activity server-side for audit and possible automated bans/warnings.

Example server event that validates authority and range before reviving:

local QBCore = exports['qb-core']:GetCoreObject()

RegisterNetEvent('qb-ambulance:server:requestRevive', function(targetId)
  local src = source
  local player = QBCore.Functions.GetPlayer(src)
  if not player or player.PlayerData.job.name ~= 'ambulance' then
    return -- unauthorized
  end

  local target = QBCore.Functions.GetPlayer(tonumber(targetId))
  if not target then return end

  -- Server-side proximity check: request coords from both players or use server tracking
  -- Here we ask the client for their coords, but verify with additional server-side logic or distance data
  TriggerClientEvent('qb-ambulance:client:confirmRevive', src, target.PlayerData.source)
end)

Implementation choices: vehicles, spawners, and ox_lib

Decide how ambulances are provided. Common options:

  • Static parking spots with a server-side spawner that checks job and duty status.
  • Use a garage resource to manage keys and storage. Ensure keys are server-tracked.
  • Optionally integrate ox_lib for menus and progress bars; keep its use client-side for UX only.

Vehicle spawn should be a server-triggered action after validation. Example pattern:

-- Server: spawn vehicle if job and spot available
RegisterNetEvent('qb-ambulance:server:spawnAmbulance', function(model)
  local src = source
  local xPlayer = QBCore.Functions.GetPlayer(src)
  if not xPlayer or xPlayer.PlayerData.job.name ~= 'ambulance' then return end

  -- server can check a "spawn queue" to avoid abuse and ensure unique instances
  TriggerClientEvent('qb-ambulance:client:spawnVehicle', src, model)
end)

Healing, revives, and billing patterns

Typical ambulance responsibilities: heal, revive, transport, and billing. For each action, follow this pattern:

  1. Client sends intent to server (e.g., "I wish to bill player X for Y").
  2. Server verifies job, proximity, target status, and amount (ensure non-negative, within configured caps).
  3. Server performs account transactions, database writes, and triggers client effects.

Example server-side billing validation:

RegisterNetEvent('qb-ambulance:server:billPlayer', function(targetId, amount)
  local src = source
  local biller = QBCore.Functions.GetPlayer(src)
  amount = tonumber(amount)
  if not biller or biller.PlayerData.job.name ~= 'ambulance' then return end
  if not amount or amount <= 0 or amount > 5000 then return end -- cap
  local target = QBCore.Functions.GetPlayer(tonumber(targetId))
  if not target then return end

  -- perform the transaction server-side
  target.Functions.RemoveMoney('bank', amount, "ambulance-bill")
  biller.Functions.AddMoney('bank', amount, "ambulance-income")
  -- record billing in DB or invoice system
end)

File structure and required entries

Keep a predictable layout in your ambulance resource. Example minimal structure:

File / Resource Purpose
fxmanifest.lua Define resource, dependencies (qb-core, ox_lib optional), and server/client scripts
server/main.lua All authoritative logic: job checks, billing, revives, DB writes
client/main.lua Animations, progress bars, UI, vehicle spawning request (non-authoritative)
config.lua Job name, revive costs, vehicle models, spawn points
locales/*.lua Localization for messages and notifications

Ensure fxmanifest enumerates dependencies to prevent startup order issues. Example dependency line: dependency 'qb-core'.

Testing and debugging

Follow a staged testing workflow:

  1. Start with server-only unit checks: validate config values and DB migrations.
  2. Use a local client to exercise each server event and inspect server logs (server console & qb-core debug). Add verbose logging temporarily around validation paths.
  3. Test edge cases: unauthorized client attempts, missing target, invalid amounts, distance manipulation.
  4. Simulate network issues: what happens if a client disconnects mid-revive? Ensure server rolls back partial transactions.

Use these debug tips:

  • Print contextual info server-side (source id, job, target id) for event handling.
  • Use breakpoints in your editor with a Lua debugger if available.
  • Review server event flood patterns to detect abuse.

Deployment and maintenance

Before deploying to production:

  • Back up your database (players and job fields).
  • Deploy to a staging server and allow staff to validate job behavior.
  • Plan for migrations: document any DB updates required for job names or permissions.

Ongoing maintenance items:

  • Monitor server logs for unauthorized attempts or errors.
  • Keep the resource compatible with qb-core updates; test after core updates.
  • Maintain security: patch any server-side logic that makes assumptions about client state.

For management and workflow tooling during your build/deploy cycle, you can pair local dev tools with the developer dashboard at https://trystellarai.com/app.

Roblox (Luau) considerations — brief

If you also maintain a Roblox server or adapt patterns from this guide to Roblox, follow these authoritative server principles:

  • Use RemoteEvents/RemoteFunctions only for intent; perform all state changes (health, currency) on the server.
  • Treat client messages as untrusted and perform proximity/permission checks server-side.
  • Handle DataStore failures using pcall and exponential backoff, and plan for writes to be retried or queued on server restart.
  • Never assume a successful DataStore write — record local transient state and ensure reconciliation on player reconnect.

Practical checklist before going live

Task Done Notes
Register ambulance job in shared jobs list Ensure name matches DB entries or migrate DB
Implement server-side revive/billing handlers Include job & proximity validation
Add vehicle spawn server-side checks Limit spawn rate and ensure unique instances
Test unauthorized client attempts Log and hard-fail on suspicious patterns
Deploy on staging and run full playtest Collect staff feedback and adjust

Common pitfalls and how to avoid them

Watch out for:

  • Client-only checks for billing or revives — always duplicate checks server-side.
  • Hardcoding job names in multiple places — centralize in config/shared files.
  • Not handling disconnects during critical flows — ensure atomic server operations.
  • Mismatched dependencies or missing fxmanifest entries causing startup failures.

Example server-to-client safe flow

Flow pattern summary:

  1. Client requests action → Server validates job, proximity, caps → Server performs DB changes and triggers client effects → Server logs event.
  2. Client only displays UI and animations after confirmation from the server.

Build your next system with Stellar AI

Describe one feature, get organized project files, then bring back your errors to keep improving. Start free with no card required. Test generated code in a private development environment before release.

Create your first script free →