FiveM & QBCore · Practical guide

QBCore Prison Script — Design, Implementation & Best Practices

A practical, server-authoritative guide to designing and implementing a robust QBCore prison/jail script for FiveM. Includes architecture, security, qb-target integration, persistence,.

Stellar AI · Updated 8 September 2026 · 5 min read

A clean, secure prison system is a common server feature that must be server-authoritative, resilient to disconnects, and easy for admins to operate. This guide walks through design choices, persistence, escape prevention, admin commands and a sample implementation pattern that fits modern QBCore servers.

Overview & implementation plan

This guide explains how to build a QBCore prison script that: enforces jail time server-side, persists jail state across disconnects, supports admin commands and qb-target interactions, and prevents exploits. Implementation plan:

  1. Decide data persistence (oxmysql or ghmattimysql) and schema changes.
  2. Create server-side APIs that validate permission and set jail state in the DB.
  3. Create a client script to teleport, limit controls, play animations and sync timers from the server.
  4. Integrate qb-target for in-world interactions and add safe admin commands.
  5. Test disconnect, reconnect, and failure scenarios.

Assumptions and framework dependencies

  • Server uses QBCore (v2+ or compatible). APIs like QBCore.Functions.GetPlayer exist.
  • MySQL resource available (oxmysql or ghmattimysql). Examples use exports.oxmysql:execute but you can swap to your DB wrapper.
  • qb-target or equivalent is installed for interaction targets.
  • The users table stores a unique identifier (steam/fivem identifier). If your schema differs, adjust SQL and metadata keys accordingly.

Core design principles

Keep authority on the server. The server must be the source of truth for who is jailed and for how long. Clients should only present visuals and report input; they must not be trusted with countdown logic, granting parole, or modifying jail state.

Server responsibilities

  • Validate admin actions and permission checks.
  • Record jail state in persistent storage.
  • Emit events to clients to apply confinement (teleport, freeze).
  • Run periodic timers and release players when time elapses.

Client responsibilities

  • Receive server events and apply local restrictions (disable controls, UI).
  • Play animations or cell camera effects.
  • Attempt reconnection gracefully and re-sync jail state on connect.

Minimal safe code example

Below is a compact pattern showing a server command to jail a player, persistent DB write, and an event emitted to the client. This is an illustrative example; update identifiers and SQL for your schema.

-- fxmanifest.lua
fx_version 'cerulean'
game 'gta5'
author 'YourName'
description 'QBCore Prison Script'
version '1.0.0'

shared_script '@qb-core/import.lua'
server_script 'server/server.lua'
client_script 'client/client.lua'
-- server/server.lua
local QBCore = exports['qb-core']:GetCoreObject()

QBCore.Commands.Add('jail', 'Jail a player', {{name='id', help='Player ID'}, {name='minutes', help='Minutes to jail'}}, true, function(source, args)
  local src = source
  local targetId = tonumber(args[1])
  local minutes = tonumber(args[2]) or 5
  local caller = QBCore.Functions.GetPlayer(src)
  if not caller or not caller.PlayerData.job or caller.PlayerData.job.name ~= 'police' then
    return TriggerClientEvent('QBCore:Notify', src, 'No permission', 'error')
  end

  local target = QBCore.Functions.GetPlayer(targetId)
  if not target then
    return TriggerClientEvent('QBCore:Notify', src, 'Player not online', 'error')
  end

  local identifier = target.PlayerData.citizenid or target.PlayerData.license
  local jailTime = os.time() + (minutes * 60)

  -- Persist jail state (example uses oxmysql)
  exports.oxmysql:execute('UPDATE users SET jail = ?, jail_time = ? WHERE citizenid = ?', {1, jailTime, identifier})

  -- Notify target client
  TriggerClientEvent('qb-prison:client:setJailed', target.PlayerData.source, minutes)
  TriggerClientEvent('QBCore:Notify', src, 'Player jailed for ' .. minutes .. ' minutes')
end, 'police')

-- On server start / player load: re-sync jailed players
AddEventHandler('QBCore:Server:PlayerLoaded', function(playerId)
  local src = playerId
  local Player = QBCore.Functions.GetPlayer(src)
  if not Player then return end
  local identifier = Player.PlayerData.citizenid
  exports.oxmysql:execute('SELECT jail, jail_time FROM users WHERE citizenid = ?', {identifier}, function(result)
    if result and result[1] and result[1].jail == 1 then
      local remaining = math.max(0, result[1].jail_time - os.time())
      if remaining > 0 then
        TriggerClientEvent('qb-prison:client:setJailed', src, math.floor(remaining / 60))
      else
        -- cleanup expired jail
        exports.oxmysql:execute('UPDATE users SET jail = ?, jail_time = ? WHERE citizenid = ?', {0, 0, identifier})
      end
    end
  end)
end)
-- client/client.lua
local jailed = false

RegisterNetEvent('qb-prison:client:setJailed', function(minutes)
  jailed = true
  -- simple example: teleport to cell, freeze and open UI
  local ped = PlayerPedId()
  DoScreenFadeOut(500)
  Wait(600)
  SetEntityCoords(ped, vector3(1790.0, 2570.0, 45.8)) -- adjust to your cell
  FreezeEntityPosition(ped, true)
  DoScreenFadeIn(500)
  -- show countdown or UI driven by server
end)

-- Prevent actions while jailed
Citizen.CreateThread(function()
  while true do
    Citizen.Wait(0)
    if jailed then
      DisableControlAction(0, 1, true) -- look
      DisableControlAction(0, 2, true) -- move
      -- keep network alive and short waits
    else
      Citizen.Wait(1000)
    end
  end
end)

Checklist: server vs client responsibilities

ResponsibilityServerClient
Validate admin action✔️
Persist jail state✔️
Timer and release✔️Display only
Teleport & freeze visualsTriggerApply
Escape detection✔️ (pos checks)Report pos

Security considerations

Never rely on client-side timers or client requests to end jail. When you release someone, update the DB and then emit a release event. On reconnect the server must re-check DB state; do not trust a reconnecting client's memory. Monitor for teleport/vehicle exploits by periodically checking jailed players' coordinates server-side and re-teleport them if they leave the cell area.

Failure & resilience

If your DB is temporarily unavailable, queue actions in memory with conservative defaults and refuse admin jail commands until persistence is confirmed, or show a clear admin error. On player load, if DB query fails, log a server warning and keep the player disconnected from jail areas until persistence is restored or an admin intervenes.

Operational tips & integrations

Use qb-target to add interaction points in the prison (visiting, release desk) and register server callbacks (server-side validation for visit requests). Test disconnect/reconnect flows thoroughly. For quick scaffolding and to auto-generate file templates, consider using the Stellar AI app to produce destination-labelled files and iteration-ready plans: https://trystellarai.com/app. When you want deeper design posts or implementation walkthroughs, read the Stellar AI blog: https://trystellarai.com/blog.

Stellar AI can help generate and revise the project files for this workflow. Describe your framework, existing dependencies, and one testable feature, then bring back errors for a focused revision. You remain responsible for running the tests and managing deployment in your own development environment.

Next steps

  • Implement periodic server-side checks that enforce position and release.
  • Add an admin UI to inspect jailed players and remaining times.
  • Extend with visitor systems, contraband detection, and jobs inside the prison.

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 →