FiveM & QBCore · Practical guide

QBCore Taxi Job: design, secure implementation, and Roblox parity notes

A practical, senior-level guide to building a QBCore taxi job for FiveM: server-authoritative fares, qb-target interactions, fxmanifest layout, file checklist, and a brief Roblox.

Stellar AI · Updated 8 September 2026 · 5 min read

This guide walks you through designing and implementing a robust QBCore taxi job: server-side fare logic, secure events, qb-target pickup interactions, and installable resource files. If you want to scaffold the project faster, open the Stellar AI project planner in the app: https://trystellarai.com/app to create files from plain-English prompts.

Why server authority matters

Taxi jobs are straightforward gameplay but attractive for exploits: money transfers, fake distances, or client-side manipulation. Make the server authoritative for trip state (active trip, pickup time), fare computation, and payment. Keep clients responsible only for UI, local markers, and requesting server actions. This minimizes cheating while keeping responsive client UX.

Design principles and constraints

  • Validate job permissions server-side: only players with job.name == 'taxi' may accept fares or be paid.
  • Compute fare server-side using stored pickup/dropoff coordinates and a safe formula; clamp for sanity.
  • Store active trips server-side with timestamps and coordinates to compute duration/distance.
  • Use qb-target or ox_target on client to start interactions but call server events to register state.

Quick implementation plan

  1. Create resource skeleton (fxmanifest.lua, config.lua, client.lua, server.lua, html/UI if needed).
  2. Expose qb-target zones to start/stop trips and use client events to call server APIs.
  3. Server: Maintain activeTrips table, compute fares, validate passenger funds, and perform atomic payment operations.
  4. Test edge cases: disconnect mid-trip, Data persistence, and unusual coordinates.

If you'd like a fast scaffold generator, you can also create a plan inside Stellar AI: https://trystellarai.com/app and export complete files.

Files, placement, and responsibilities (practical checklist)

PathPurpose
qb-taxi/fxmanifest.luaResource manifest
qb-taxi/config.luaConfig: fares, clamp values, target models
qb-taxi/client.luaqb-target interactions, blips, UI triggers
qb-taxi/server.luaActive trip tracking, fare calc, payment, validation
qb-taxi/html/*Optional UI (ride request panel)

Example files and safe code snippets

fxmanifest to start:

fx_version 'cerulean'
games { 'gta5' }

author 'you'
description 'QBCore Taxi job'
version '1.0.0'

shared_script 'config.lua'
server_script 'server.lua'
client_script 'client.lua'

Server: authoritative trip completion and payment (QBCore conventions)

-- server.lua
local QBCore = exports['qb-core']:GetCoreObject()
local activeTrips = {} -- key: driverSource -> { passenger = id, pickup = vec3, started = os.time() }

RegisterNetEvent('qb-taxi:server:StartTrip', function(passengerId, pickupX, pickupY, pickupZ)
    local src = source
    local driver = QBCore.Functions.GetPlayer(src)
    if not driver or driver.PlayerData.job.name ~= 'taxi' then return end
    -- Basic validation of values
    local pickup = { x = tonumber(pickupX) or 0, y = tonumber(pickupY) or 0, z = tonumber(pickupZ) or 0 }
    activeTrips[src] = { passenger = passengerId, pickup = pickup, started = os.time() }
    TriggerClientEvent('QBCore:Notify', src, 'Trip started', 'success')
end)

RegisterNetEvent('qb-taxi:server:CompleteTrip', function(dropX, dropY, dropZ)
    local src = source
    local trip = activeTrips[src]
    if not trip then return end
    local driver = QBCore.Functions.GetPlayer(src)
    if not driver or driver.PlayerData.job.name ~= 'taxi' then
        activeTrips[src] = nil
        return
    end
    local passenger = QBCore.Functions.GetPlayer(tonumber(trip.passenger))
    if not passenger then
        activeTrips[src] = nil
        return
    end
    -- Simple distance-based fare (server-side)
    local dx = dropX - trip.pickup.x
    local dy = dropY - trip.pickup.y
    local distance = math.sqrt(dx*dx + dy*dy)
    local baseFare = 50
    local perMeter = 0.5
    local fare = math.floor(baseFare + (distance * perMeter))
    -- Clamp fare to server policy
    fare = math.max(20, math.min(fare, 2000))
    -- Check passenger funds and perform payment atomically
    local success = false
    if passenger.Functions.RemoveMoney then
        -- remove from passenger then add to driver
        local removed = passenger.Functions.RemoveMoney('bank', fare, 'taxi_fare')
        if removed then
            driver.Functions.AddMoney('bank', fare, 'taxi_income')
            success = true
        end
    end
    if success then
        TriggerClientEvent('QBCore:Notify', src, 'Trip complete: $' .. fare, 'success')
        TriggerClientEvent('QBCore:Notify', passenger.PlayerData.source, 'You paid $' .. fare .. ' for the ride', 'success')
    else
        TriggerClientEvent('QBCore:Notify', src, 'Trip failed: passenger could not pay', 'error')
    end
    activeTrips[src] = nil
end)

Client: qb-target example to start a trip on a vehicle or NPC. Keep it UI-only and call server events.

-- client.lua (snippet)
local QBCore = exports['qb-core']:GetCoreObject()
-- qb-target setup, run once
exports['qb-target']:AddTargetEntity(vehicleEntity, {
    options = {
        { type = 'client', event = 'qb-taxi:client:RequestPickup', icon = 'fa fa-taxi', label = 'Request Taxi' }
    }, distance = 3.5
})

RegisterNetEvent('qb-taxi:client:RequestPickup', function()
    local ped = PlayerPedId()
    local pos = GetEntityCoords(ped)
    TriggerServerEvent('qb-taxi:server:RequestTaxi', pos.x, pos.y, pos.z)
end)

Validation and security checklist

  • Only accept StartTrip/CompleteTrip from players whose server-side job == 'taxi'.
  • Clamp fare to min/max; do not accept client-supplied fare amount.
  • Verify passenger player is connected before trying to debit money.
  • Handle disconnects by expiring activeTrips after a timeout server-side.
  • Log suspicious events for manual review (excessive distance or repeated failures).

Roblox parity notes (server/client separation)

If you want a similar Taxi job on Roblox: keep the server authoritative. Use RemoteEvents for requests and server Scripts to store active rides. For persistent earnings, use DataStoreService with pcall and retries — never trust client-sent fares. Example: server Script listens to a RemoteEvent 'RequestTaxi', validates player and assigns driver, then when trip ends it updates the driver's leaderstats and writes to DataStore with safe error handling.

-- Roblox Server Script (snippet)
local Remote = game.ReplicatedStorage:WaitForChild('TaxiRemote')
local DataStore = game:GetService('DataStoreService'):GetDataStore('TaxiEarnings')
Remote.OnServerEvent:Connect(function(player, action, params)
    if action == 'CompleteTrip' then
        local fare = math.clamp(math.floor(params.distance * 0.5) + 50, 20, 2000)
        -- Update leaderstats server-side
        player.leaderstats.Cash.Value = player.leaderstats.Cash.Value + fare
        -- Save earnings safely
        local success, err = pcall(function()
            DataStore:SetAsync('earnings_'..player.UserId, player.leaderstats.Cash.Value)
        end)
        if not success then warn('DataStore save failed', err) end
    end
end)

Next steps

Test the resource in a development server, simulate edge cases (disconnects, insufficient funds), and iterate. If you prefer a prompt-driven scaffold that produces complete destination-labelled files, open the project planner in the Stellar AI app: https://trystellarai.com/app. For deeper reading on job patterns and server authority, see the Stellar AI blog: https://trystellarai.com/blog

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 →