FiveM & QBCore · Practical guide

QBCore Racing Script — Server-Authoritative Race and Lap Timer

Step-by-step guide to build a server-authoritative QBCore racing resource for FiveM. Includes a short plan, assumptions, complete destination-labelled files, validation and security.

Stellar AI · Updated 8 September 2026 · 5 min read

This guide shows how to implement a simple, server-authoritative racing script for QBCore-powered FiveM servers. It focuses on secure checkpoint sequencing, lap timing, and a minimal persistent best-time saving example. You’ll get a short implementation plan, assumptions, complete resource files (fxmanifest, client, server, config), installation steps, checks, and suggested next steps.

Overview

Racing scripts are a popular server activity, but naive client-side timers are easy to cheat. This guide builds a small QBCore resource where the server controls race state: players start, pass checkpoints in order, and the server computes lap and finish times. Clients only send checkpoint index events; the server validates order and stores best times. That design minimizes trust in the client while remaining performant and easy to extend.

Implementation plan (short)

  1. Define checkpoint positions and lap count in config.lua.
  2. Client shows markers and detects local proximity; when a checkpoint is passed, send the index to the server.
  3. Server tracks per-player race state (next checkpoint, lap start time). Server computes lap/finish times and validates ordering.
  4. On finish, save best time to your DB using your available MySQL library (example uses oxmysql; replace if needed).

Assumptions & dependencies

  • Your server runs QBCore (modern export: exports['qb-core']:GetCoreObject()).
  • A MySQL driver is available for persistence. Example uses exports['oxmysql']:execute. Replace with ghmattimysql, mysql-async, or your chosen wrapper if different.
  • This example is minimal: integrate qb-target, ox_lib, UI menus, or server-side race scheduling to suit your server.

Files (complete, destination-labelled)

Place these files in a resource folder named qb-racing (or your chosen name). Folder structure:

  • qb-racing/fxmanifest.lua
  • qb-racing/config.lua
  • qb-racing/client/main.lua
  • qb-racing/server/main.lua

fxmanifest.lua

fx_version 'cerulean'
games { 'gta5' }

author 'YourName'
description 'QBCore Racing Script'
version '1.0.0'

dependency 'qb-core'

shared_script 'config.lua'
client_scripts { 'client/main.lua' }
server_scripts { 'server/main.lua' }

config.lua

Config = {}
-- Simple sample: three checkpoints forming a short circuit
Config.Checkpoints = {
  { x = 215.0, y = -810.0, z = 29.73, radius = 6.0 },
  { x = 255.0, y = -820.0, z = 29.73, radius = 6.0 },
  { x = 240.0, y = -790.0, z = 29.73, radius = 6.0 }
}
Config.Laps = 3

client/main.lua

local QBCore = exports['qb-core']:GetCoreObject()
local nextIndex = 1
local racing = false
local startTime = 0

-- Draw simple markers and check proximity
Citizen.CreateThread(function()
  while true do
    Citizen.Wait(500)
    local ped = PlayerPedId()
    local pos = GetEntityCoords(ped)
    if not racing then
      -- idle; no need to draw all markers every tick in production
    else
      local cp = Config.Checkpoints[nextIndex]
      if cp then
        DrawMarker(1, cp.x, cp.y, cp.z - 1.0, 0,0,0, 0,0,0, 2.0,2.0,1.0, 255,0,0,100, false, true, 2, nil, nil, false)
        local dist = #(pos - vector3(cp.x, cp.y, cp.z))
        if dist <= cp.radius then
          -- pass locally and notify server
          TriggerServerEvent('qb-race:checkpoint', nextIndex)
          nextIndex = nextIndex + 1
          if nextIndex > #Config.Checkpoints then
            -- completed a lap locally; wrap
            nextIndex = 1
          end
          Citizen.Wait(1000) -- simple debounce
        end
      end
    end
  end
end)

-- Commands for testing: start/stop
RegisterCommand('startrace', function()
  racing = true
  nextIndex = 1
  TriggerServerEvent('qb-race:start')
  QBCore.Functions.Notify('Race started!')
end)

RegisterCommand('stoprace', function()
  racing = false
  nextIndex = 1
  TriggerServerEvent('qb-race:stop')
  QBCore.Functions.Notify('Race stopped')
end)

server/main.lua

local QBCore = exports['qb-core']:GetCoreObject()
local raceState = {} -- keyed by source while in race

RegisterNetEvent('qb-race:start', function()
  local src = source
  local player = QBCore.Functions.GetPlayer(src)
  if not player then return end
  raceState[src] = {
    nextIndex = 1,
    lap = 1,
    startTime = os.time()
  }
  TriggerClientEvent('QBCore:Notify', src, 'Server: race started')
end)

RegisterNetEvent('qb-race:stop', function()
  local src = source
  raceState[src] = nil
end)

RegisterNetEvent('qb-race:checkpoint', function(index)
  local src = source
  local state = raceState[src]
  if not state then return end
  -- validate order
  if index ~= state.nextIndex then
    -- out of order: ignore or punish
    return
  end
  -- advance
  state.nextIndex = state.nextIndex + 1
  if state.nextIndex > #Config.Checkpoints then
    -- completed a lap
    local now = os.time()
    local lapTime = now - state.startTime
    state.lap = state.lap + 1
    state.startTime = now
    state.nextIndex = 1
    -- if finished all laps
    if state.lap > Config.Laps then
      -- finished race
      local player = QBCore.Functions.GetPlayer(src)
      local citizenid = player.PlayerData.citizenid
      -- Save best time example: replace with your DB method if needed
      exports['oxmysql']:execute('INSERT INTO race_times (citizenid, time) VALUES (?, ?)',[citizenid, lapTime])
      TriggerClientEvent('QBCore:Notify', src, ('Race finished! Final lap time: %s seconds'):format(lapTime))
      raceState[src] = nil
      return
    else
      TriggerClientEvent('QBCore:Notify', src, ('Lap complete! Lap time: %s seconds'):format(lapTime))
    end
  end
end)

Installation & deployment

  1. Create resource folder (qb-racing) and add the files above.
  2. Ensure fxmanifest.lua lists qb-core as dependency and your SQL wrapper is present on the server.
  3. Add your DB table (race_times) or change the save code to your existing schema. Do not store secrets in code.
  4. Add start/stop commands or a UI integration (qb-menu / qb-core exports) as you prefer.
  5. Start the resource and test with a client in a controlled environment.

If you want a workspace to plan versions and generate destination-labelled files faster, consider generating the project plan in the Stellar AI app: https://trystellarai.com/app — it helps structure file outputs and iterations. After you have a working prototype, you can import the plan again in the app to expand features: https://trystellarai.com/app.

Validation, security & best practices

  • Server-authoritative checks: never accept times from the client. The server must compute lap and finish times using server-time and checkpoint order.
  • Order enforcement: server must reject checkpoint events that are out of order. Optionally log or ban repeated cheating attempts.
  • Database calls: sanitize input using parameterized queries (the example uses parameter placeholders). Do not log or store raw player tokens or secrets.
  • Performance: avoid checking distances every frame for all players. The sample checks every 500ms. For many players, consider partitioning or using qb-target to reduce overhead.
  • Persistence: record best times per citizenid, but consider rate limits and aggregation if races run frequently.

Checklist

TaskAction
Config checkpointsDefine positions and radius in config.lua
Server validationEnsure server computes times and enforces checkpoint order
DB integrationReplace example oxmysql call with your SQL library and test inserts
Client UIAdd in-game start UI / qb-menu if needed
Security testTry sending out-of-order events from a client and verify rejection

Next steps and tuning

After the basic server-authoritative loop works, you can add features: timed leaderboards, spectator mode, ghost replays, bet pools, or race lobbies. If you want help iterating your plan into full destination-labelled files or expanding to UI/menus, use the Stellar AI blog for inspiration: https://trystellarai.com/blog, or import your requirements into the app to produce concrete file outputs and revisions.

This guide aims to give a secure, minimal starting point. On production servers, test thoroughly with multiple players and review DB performance and anti-cheat logs before opening to the public.

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 →