Guide 2026-04-14

Creating a Court & Legal System for FiveM Roleplay

OntelMonke

OntelMonke

Admin & Developer at Agency Scripts

Legal System Architecture

A court and legal system elevates a FiveM roleplay server from basic cops-and-robbers gameplay into a structured society where laws have weight and consequences follow due process. The system encompasses warrants that police use to authorize searches and arrests, a court hearing workflow where cases are presented before a judge, defense and prosecution roles for lawyers, an evidence management system that tracks physical and digital proof, and a sentencing framework for fines, jail time, and alternative punishments. The architecture centers on a case management database that tracks every legal proceeding from the initial charge through arraignment, trial, and verdict. Each case links to the involved parties, their roles, submitted evidence, and the final outcome. This data persists permanently, building a legal history for the server that players and officials can reference in future proceedings.

Database Schema for Legal Records

Your database needs to handle warrants, court cases, evidence items, and legal records. The schema connects police investigations to court proceedings through a shared evidence system where items collected during an investigation become exhibits presented at trial. Design the tables to maintain a complete audit trail of every action taken in a legal proceeding:

CREATE TABLE IF NOT EXISTS warrants (
    id INT AUTO_INCREMENT PRIMARY KEY,
    type ENUM('arrest', 'search', 'bench') NOT NULL,
    target_citizenid VARCHAR(50) NOT NULL,
    target_name VARCHAR(100) NOT NULL,
    reason TEXT NOT NULL,
    issued_by VARCHAR(50) NOT NULL,
    approved_by VARCHAR(50) DEFAULT NULL,
    status ENUM('pending', 'active', 'executed', 'expired', 'revoked') DEFAULT 'pending',
    issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expires_at TIMESTAMP NULL,
    executed_at TIMESTAMP NULL,
    INDEX idx_target (target_citizenid),
    INDEX idx_status (status)
);

CREATE TABLE IF NOT EXISTS court_cases (
    id INT AUTO_INCREMENT PRIMARY KEY,
    case_number VARCHAR(20) UNIQUE NOT NULL,
    defendant_citizenid VARCHAR(50) NOT NULL,
    defendant_name VARCHAR(100) NOT NULL,
    prosecutor_citizenid VARCHAR(50) DEFAULT NULL,
    defense_citizenid VARCHAR(50) DEFAULT NULL,
    judge_citizenid VARCHAR(50) DEFAULT NULL,
    charges TEXT NOT NULL,
    status ENUM('filed', 'arraignment', 'pretrial', 'trial', 'verdict', 'closed', 'dismissed') DEFAULT 'filed',
    plea ENUM('not_guilty', 'guilty', 'no_contest') DEFAULT NULL,
    verdict ENUM('guilty', 'not_guilty', 'mistrial', 'dismissed') DEFAULT NULL,
    sentence TEXT DEFAULT NULL,
    fine_amount INT DEFAULT 0,
    jail_minutes INT DEFAULT 0,
    filed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    hearing_scheduled TIMESTAMP NULL,
    closed_at TIMESTAMP NULL,
    INDEX idx_defendant (defendant_citizenid),
    INDEX idx_status (status)
);

CREATE TABLE IF NOT EXISTS evidence (
    id INT AUTO_INCREMENT PRIMARY KEY,
    case_id INT DEFAULT NULL,
    type ENUM('physical', 'digital', 'testimony', 'document', 'photo') NOT NULL,
    label VARCHAR(200) NOT NULL,
    description TEXT DEFAULT NULL,
    collected_by VARCHAR(50) NOT NULL,
    collected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    chain_of_custody TEXT DEFAULT NULL,
    is_admitted BOOLEAN DEFAULT FALSE,
    FOREIGN KEY (case_id) REFERENCES court_cases(id)
);

CREATE TABLE IF NOT EXISTS legal_records (
    id INT AUTO_INCREMENT PRIMARY KEY,
    citizenid VARCHAR(50) NOT NULL,
    record_type ENUM('conviction', 'acquittal', 'fine', 'warning', 'restraining_order') NOT NULL,
    description TEXT NOT NULL,
    case_id INT DEFAULT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    expunged BOOLEAN DEFAULT FALSE,
    INDEX idx_citizen (citizenid),
    FOREIGN KEY (case_id) REFERENCES court_cases(id)
);

Warrant System

Warrants are the legal gateway between police investigation and action. An arrest warrant authorizes officers to detain a specific person, a search warrant permits searching a property or vehicle, and a bench warrant is issued by a judge when someone fails to appear in court. Police officers create warrant requests through the MDT that go into a pending queue for judicial review. A judge or authorized official reviews the request, examines the probable cause description, and either approves or denies it. Approved warrants become active and appear on the police MDT when officers interact with the targeted individual, alerting them that a warrant exists. Implement warrant expiration so that warrants do not persist forever, with arrest warrants lasting 7 real-time days and search warrants lasting 48 hours after approval:

-- Warrant request by police
RegisterNetEvent('legal:server:requestWarrant', function(data)
    local src = source
    local Officer = QBCore.Functions.GetPlayer(src)
    if not Officer or Officer.PlayerData.job.name ~= 'police' then return end

    local warrant = {
        type = data.type,
        target_citizenid = data.targetCitizenid,
        target_name = data.targetName,
        reason = data.reason,
        issued_by = Officer.PlayerData.citizenid,
    }

    local id = MySQL.insert.await(
        'INSERT INTO warrants (type, target_citizenid, target_name, reason, issued_by) VALUES (?, ?, ?, ?, ?)',
        { warrant.type, warrant.target_citizenid, warrant.target_name, warrant.reason, warrant.issued_by }
    )

    -- Notify judges
    local players = QBCore.Functions.GetQBPlayers()
    for _, player in pairs(players) do
        if player.PlayerData.job.name == 'judge' then
            TriggerClientEvent('legal:client:warrantPending', player.PlayerData.source, {
                id = id,
                type = warrant.type,
                targetName = warrant.target_name,
                reason = warrant.reason,
                officerName = Officer.PlayerData.charinfo.firstname .. ' ' .. Officer.PlayerData.charinfo.lastname,
            })
        end
    end

    TriggerClientEvent('QBCore:Notify', src, 'Warrant request submitted for judicial review', 'success')
end)

-- Judge approval
RegisterNetEvent('legal:server:approveWarrant', function(warrantId)
    local src = source
    local Judge = QBCore.Functions.GetPlayer(src)
    if not Judge or Judge.PlayerData.job.name ~= 'judge' then return end

    local warrant = MySQL.single.await('SELECT * FROM warrants WHERE id = ? AND status = "pending"', { warrantId })
    if not warrant then return end

    local expiryHours = warrant.type == 'search' and 48 or 168
    local expiresAt = os.date('!%Y-%m-%d %H:%M:%S', os.time() + (expiryHours * 3600))

    MySQL.update('UPDATE warrants SET status = "active", approved_by = ?, expires_at = ? WHERE id = ?',
        { Judge.PlayerData.citizenid, expiresAt, warrantId })

    -- Notify requesting officer if online
    TriggerClientEvent('QBCore:Notify', src, 'Warrant #' .. warrantId .. ' approved', 'success')
end)

Court Hearing Workflow

Court hearings follow a structured workflow that moves cases through arraignment, pretrial motions, trial, and sentencing. When a case is filed, the system generates a unique case number and assigns it a status of filed. A judge reviews the case and schedules an arraignment hearing where the defendant enters a plea. If the defendant pleads not guilty, the case proceeds to pretrial where lawyers can submit motions and evidence, then to trial where both sides present their arguments. The judge controls the flow of the hearing through commands that advance the case status and trigger UI updates for all participants. Implement a courtroom NUI that displays the case information, current phase, and provides role-specific controls for the judge, prosecutor, and defense attorney. The judge gets verdict buttons and sentencing inputs, while lawyers get objection buttons and evidence presentation controls.

Lawyer Role and Defense System

The lawyer job creates an entire career path on the server with distinct gameplay mechanics. Defense attorneys can be hired by defendants through a lawyer directory accessible from jail or the phone app. When a lawyer accepts a client, they gain access to the case file in the legal system, including all evidence submitted by the prosecution. Lawyers can file motions to suppress evidence, request continuances to delay hearings, negotiate plea deals with the prosecutor, and present their own evidence and witness testimony during trial. Implement a bar exam system where new lawyer characters must pass a knowledge test before being granted the lawyer job, ensuring a minimum level of legal roleplay quality. Track lawyer statistics like cases won, cases lost, and average sentence reduction for their clients, creating a competitive marketplace where established lawyers can charge higher fees:

Config.LawyerSystem = {
    barExamQuestions = 15,
    passingScore = 11,       -- 73% to pass
    examCooldown = 86400,    -- 24 hours between attempts
    maxActiveClients = 5,
    consultationFee = 500,   -- base fee for initial consultation
    trialFeeRange = { min = 2000, max = 25000 },

    motionTypes = {
        { id = 'suppress_evidence', label = 'Motion to Suppress Evidence', description = 'Request exclusion of illegally obtained evidence' },
        { id = 'dismiss_charges', label = 'Motion to Dismiss', description = 'Request dismissal due to insufficient evidence' },
        { id = 'continuance', label = 'Motion for Continuance', description = 'Request to postpone the hearing' },
        { id = 'change_venue', label = 'Motion for Change of Venue', description = 'Request trial at different location' },
        { id = 'bail_reduction', label = 'Motion for Bail Reduction', description = 'Request lower bail amount' },
        { id = 'expungement', label = 'Motion for Expungement', description = 'Request to clear prior conviction' },
    },
}

-- Hire a lawyer from jail
RegisterNetEvent('legal:server:hireLawyer', function(lawyerCitizenid)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local citizenid = Player.PlayerData.citizenid

    -- Find active case for this player
    local activeCase = MySQL.single.await(
        'SELECT * FROM court_cases WHERE defendant_citizenid = ? AND status NOT IN ("closed", "dismissed") ORDER BY filed_at DESC LIMIT 1',
        { citizenid }
    )
    if not activeCase then
        TriggerClientEvent('QBCore:Notify', src, 'No active case found', 'error')
        return
    end

    -- Assign lawyer to case
    MySQL.update('UPDATE court_cases SET defense_citizenid = ? WHERE id = ?',
        { lawyerCitizenid, activeCase.id })

    -- Notify lawyer
    local lawyerPlayer = QBCore.Functions.GetPlayerByCitizenId(lawyerCitizenid)
    if lawyerPlayer then
        TriggerClientEvent('QBCore:Notify', lawyerPlayer.PlayerData.source,
            'New client: ' .. Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname ..
            ' | Case #' .. activeCase.case_number, 'success')
    end

    TriggerClientEvent('QBCore:Notify', src, 'Attorney assigned to your case', 'success')
end)

Evidence Management System

The evidence system provides a chain of custody that connects crime scene investigation to courtroom presentation. When police collect evidence at a scene using an evidence collection command, the system creates an evidence record with the collecting officer's identity, timestamp, and location. Each piece of evidence gets a unique identifier that persists through the entire legal process. Evidence types include physical items like weapons, drugs, and clothing, digital evidence like phone records and CCTV footage, witness testimony recorded as text entries, and documents like financial records or forged IDs. Lawyers can challenge evidence admissibility through motions that question the collection method or chain of custody. The judge reviews these motions and can mark evidence as inadmissible, removing it from the case file. Implement an evidence locker at the police station where physical evidence items are stored, using inventory system integration to track the actual items collected during investigations.

Fine and Bail Mechanics

Fines and bail create economic consequences within the legal system. When a defendant is arraigned, the judge sets a bail amount based on the severity of charges and the defendant's criminal history. The defendant can pay bail through the court system to be released from custody while awaiting trial, or remain in jail until the hearing. Bail payment deducts from the player's bank account and is refunded after the case concludes if the defendant attended all hearings. If the defendant fails to appear, the bail is forfeited and a bench warrant is issued. Fines are imposed as part of sentencing and must be paid within a set timeframe. Unpaid fines accrue interest and can trigger additional legal consequences like driver's license suspension or property liens. Implement a fine payment plan system for large fines where the defendant can make weekly installments rather than paying the full amount at once, adding a persistent financial obligation that keeps the legal system relevant to the player's daily experience on the server.

Judge Commands and Sentencing

Judges need a comprehensive command set to manage court proceedings and enforce the legal system. Core commands include /case to view and manage cases, /warrant to approve or deny warrant requests, /sentence to issue jail time and fines, /bail to set bail amounts, /gag to mute disruptive players during hearings, and /contempt to jail players who disrespect the court. The sentencing command should reference a penal code configuration that defines minimum and maximum sentences for each charge, ensuring consistency across different judges. Implement a sentencing guidelines system that suggests appropriate punishments based on the charges and the defendant's criminal history, while still allowing judges the discretion to deviate with justification. Store all judicial decisions in the legal records table so that the server builds a body of precedent that experienced players can reference in future proceedings. Add a judicial review process where sentences can be appealed to a higher-ranked judge or a panel, creating additional layers of legal roleplay for dedicated players.

Integration with Police, Prison, and MDT

The legal system must integrate seamlessly with existing police, prison, and MDT resources to form a cohesive justice pipeline. When police file charges through the MDT, the system should automatically create a court case with the appropriate case number and link any evidence already collected during the investigation. When a judge sentences a defendant to jail time, the system should call the prison resource's jailing export function directly so that the sentence flows automatically without requiring a police officer to manually execute a jail command. Active warrants should appear on the police MDT when officers search for a citizen, with clear indicators showing the warrant type, issuing authority, and expiration date. Criminal records generated by court verdicts should be accessible in the MDT under the citizen's profile, showing their complete legal history including convictions, acquittals, active cases, and outstanding fines. Export all legal system functions so that other resources can check warrant status, verify lawyer credentials, look up criminal records, and interact with the court system programmatically.

Share this article

Ready to upgrade your server?

Check out our premium FiveM scripts in the Agency Scripts store or join our Discord community for support and updates.