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.