Tutorial 2026-04-29

FiveM Parking System & Meter Development

OntelMonke

OntelMonke

Admin & Developer at Agency Scripts

Understanding Parking System Architecture

A parking system adds depth and realism to any FiveM roleplay server by giving players designated areas to leave their vehicles with consequences for ignoring the rules. At its foundation, a parking system tracks which parking spots are occupied, monitors how long vehicles have been parked, and handles payment through meters or permits. The system consists of server-side logic for tracking state and processing payments, client-side detection for parking zone entry and exit, and an optional NUI interface for displaying meter status and payment options. A well-implemented parking system also integrates with your existing police and towing scripts so that illegally parked vehicles can be ticketed or impounded by authorized players.

Defining Parking Zones and Spots

Parking zones are defined as polygonal or box-shaped areas on the map, each containing individual parking spots with specific coordinates and headings. Use a configuration file to define these zones so server administrators can add new parking areas without modifying code. Each zone can have different rules, such as free parking, metered parking, or permit-only areas. Here is a practical zone configuration structure:

Config.ParkingZones = {
    ['pillbox_lot'] = {
        label = 'Pillbox Medical Parking',
        type = 'metered',       -- 'free', 'metered', 'permit'
        rate = 50,              -- $ per hour (metered only)
        maxTime = 120,          -- minutes max parking
        blip = {sprite = 357, color = 3, scale = 0.6},
        spots = {
            {coords = vector4(310.5, -590.2, 43.3, 70.0), occupied = false},
            {coords = vector4(313.8, -591.6, 43.3, 70.0), occupied = false},
            {coords = vector4(317.1, -593.0, 43.3, 70.0), occupied = false},
            {coords = vector4(320.4, -594.4, 43.3, 70.0), occupied = false},
        },
        fineAmount = 250,       -- fine for overtime or illegal parking
    },
    ['legion_street'] = {
        label = 'Legion Square Parking',
        type = 'metered',
        rate = 75,
        maxTime = 60,
        blip = {sprite = 357, color = 3, scale = 0.6},
        spots = {
            {coords = vector4(215.2, -810.5, 30.7, 160.0), occupied = false},
            {coords = vector4(212.6, -810.5, 30.7, 160.0), occupied = false},
        },
        fineAmount = 350,
    },
}

The vector4 format stores x, y, z coordinates along with the heading angle, ensuring vehicles are parked facing the correct direction. The occupied flag is managed at runtime by the server and is not saved between restarts since vehicles are tracked separately through the garage system database. Keep the spot count realistic for each location, because having too many spots in a small area leads to vehicle clipping issues.

Parking Meter Timer System

The meter system tracks how long each vehicle has been parked and charges accordingly. When a player parks in a metered zone, they interact with a meter to start their session, selecting a duration and paying upfront. The server stores the session start time, paid duration, plate number, and zone ID. A server-side timer thread periodically checks all active meter sessions and marks expired ones for enforcement. Here is the core meter logic:

local ActiveMeters = {} -- keyed by plate

RegisterNetEvent('parking:server:startMeter', function(plate, zoneId, duration)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    local zone = Config.ParkingZones[zoneId]
    if not zone or zone.type ~= 'metered' then return end

    -- Calculate cost based on duration in minutes
    local hours = duration / 60
    local cost = math.ceil(hours * zone.rate)

    if Player.Functions.RemoveMoney('cash', cost, 'parking-meter') then
        ActiveMeters[plate] = {
            zone = zoneId,
            startTime = os.time(),
            paidUntil = os.time() + (duration * 60),
            plate = plate,
            owner = Player.PlayerData.citizenid,
        }
        TriggerClientEvent('parking:client:meterStarted', src, duration, cost)
    else
        TriggerClientEvent('QBCore:Notify', src, 'Not enough cash', 'error')
    end
end)

-- Check for expired meters every 60 seconds
CreateThread(function()
    while true do
        Wait(60000)
        local now = os.time()
        for plate, meter in pairs(ActiveMeters) do
            if now > meter.paidUntil then
                -- Meter expired, issue fine
                IssueParkingFine(plate, meter.zone, meter.owner)
                ActiveMeters[plate] = nil
            end
        end
    end
end)

The timer resolution of 60 seconds is sufficient for parking enforcement without creating unnecessary CPU load. Store meter sessions in memory rather than the database since they are temporary and do not need to survive server restarts. When the server restarts, all meters reset, which is acceptable because vehicles also return to garages on restart in most frameworks.

Vehicle Detection and Spot Assignment

Detecting when a vehicle enters or leaves a parking spot requires periodic proximity checks on the client side. Use a distance-based check against all spots in nearby zones rather than checking every spot on the entire map. Only run the detection loop when the player is in a vehicle and near a configured parking zone. This keeps CPU usage minimal during normal gameplay:

-- client/detection.lua
local nearestZone = nil
local currentSpot = nil

CreateThread(function()
    while true do
        local sleep = 1000
        local ped = PlayerPedId()
        local inVehicle = IsPedInAnyVehicle(ped, false)

        if inVehicle then
            local veh = GetVehiclePedIsIn(ped, false)
            local vehCoords = GetEntityCoords(veh)

            for zoneId, zone in pairs(Config.ParkingZones) do
                local firstSpot = zone.spots[1]
                local zoneDist = #(vehCoords - vector3(firstSpot.coords.x,
                    firstSpot.coords.y, firstSpot.coords.z))

                if zoneDist < 50.0 then
                    sleep = 200
                    nearestZone = zoneId

                    for i, spot in ipairs(zone.spots) do
                        local spotPos = vector3(spot.coords.x, spot.coords.y, spot.coords.z)
                        local dist = #(vehCoords - spotPos)
                        if dist < 3.0 then
                            local speed = GetEntitySpeed(veh)
                            if speed < 0.5 then
                                currentSpot = {zone = zoneId, index = i}
                                ShowParkingPrompt(zone)
                            end
                        end
                    end
                    break
                end
            end
        else
            nearestZone = nil
            currentSpot = nil
        end

        Wait(sleep)
    end
end)

The speed check of 0.5 ensures the parking prompt only appears when the vehicle is nearly stationary, preventing the UI from flickering as the player drives through a lot. The adaptive sleep interval of 200ms near zones versus 1000ms elsewhere balances responsiveness with performance. For servers with many parking zones, consider using a spatial hash or quadtree to avoid iterating through every zone on each tick.

Parking Fine and Enforcement System

When a meter expires or a vehicle is parked illegally, the system issues a fine linked to the vehicle owner. Fines are stored in a database table and displayed when the owner next logs in or retrieves their vehicle. Police players can also manually issue parking tickets using a command or target interaction. The fine record includes the plate, amount, issuing reason, and timestamp:

function IssueParkingFine(plate, zoneId, citizenid)
    local zone = Config.ParkingZones[zoneId]
    local amount = zone and zone.fineAmount or 250

    MySQL.insert([[
        INSERT INTO parking_fines (plate, citizenid, amount, reason, zone, issued_at)
        VALUES (?, ?, ?, ?, ?, NOW())
    ]], {plate, citizenid, amount, 'Expired parking meter', zoneId})

    -- Notify owner if online
    local Player = QBCore.Functions.GetPlayerByCitizenId(citizenid)
    if Player then
        TriggerClientEvent('QBCore:Notify', Player.PlayerData.source,
            'Parking fine issued: $' .. amount, 'error')
    end
end

-- Police manual ticket command
RegisterNetEvent('parking:server:issueTicket', function(plate, reason)
    local src = source
    local Player = QBCore.Functions.GetPlayer(src)
    if not Player then return end

    if Player.PlayerData.job.name ~= 'police' then return end

    local vehicle = MySQL.query.await(
        'SELECT citizenid FROM player_vehicles WHERE plate = ?', {plate}
    )

    if vehicle and vehicle[1] then
        IssueParkingFine(plate, 'manual', vehicle[1].citizenid)
        TriggerClientEvent('QBCore:Notify', src, 'Ticket issued for ' .. plate, 'success')
    end
end)

Integrate the fine system with your vehicle retrieval process so that players must pay outstanding fines before taking a vehicle out of the garage. This creates a natural enforcement loop where ignoring parking rules has real in-game consequences. Consider adding a grace period of five to ten minutes after meter expiry before issuing the fine, as this mirrors real-world parking enforcement and reduces player frustration from perfectly-timed fines.

NUI Meter Display and Payment Interface

The parking meter NUI should be clean and informative, showing the current zone name, rate per hour, remaining time if already paid, and buttons for selecting duration and making payment. Display a countdown timer that updates every second when a meter is active so the player knows exactly how much time they have left. Use a compact design that does not obstruct the game view, similar to a real-world parking meter screen. Animate the timer display with a color transition from green to yellow to red as time runs out, providing an intuitive visual cue without requiring the player to read the exact minutes remaining. Keep the NUI lightweight by using vanilla JavaScript and minimal CSS, avoiding heavy frameworks that add loading time for a simple interface.

Towing Integration and Abandoned Vehicles

Connect your parking system with a towing mechanic so that expired or illegally parked vehicles can be towed to the impound lot. When a vehicle has been in violation for longer than the configured threshold, mark it as towable in the server state. Tow truck job players can then see these vehicles highlighted on their map and earn payment for towing them. The towing process should update the vehicle state in the database from "out" to "impounded" and record the impound reason as a parking violation. For abandoned vehicles that have been sitting in spots for extended periods without any player interaction, implement an automatic cleanup routine that moves them to impound after a configurable timeout, freeing up spots for active players. This prevents parking lots from filling up with vehicles belonging to players who have not logged in for days or weeks.

Performance and Scalability Considerations

Parking systems run continuous detection loops that can impact performance if not optimized carefully. The client-side vehicle detection should only activate for zones within render distance, and the server-side meter check thread should batch-process all active sessions rather than creating individual timers per vehicle. Index the parking fines database table on both the plate and citizenid columns to ensure fast lookups when players retrieve vehicles or when admins query fine history. For servers with dozens of parking zones and hundreds of simultaneous vehicles, consider caching zone data in a Lua table rather than reading from config on every check, and use the native function GetClosestVehicle sparingly since it scans all vehicles in range. Profile your parking resource with the FiveM built-in profiler under peak load to identify any hotspots, and remember that a parking system should use less than 0.1ms of server tick time to avoid impacting overall server performance.

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.