Tow Truck System Overview
A tow truck and impound system creates an entire job ecosystem on your FiveM server, giving players a civilian career path that interacts naturally with law enforcement, mechanics, and other drivers. The system involves multiple interconnected components: a job management layer that handles clocking in and out, a dispatch system that routes tow requests to available drivers, vehicle attachment mechanics that simulate realistic towing, an impound lot that stores seized and abandoned vehicles, and a fee structure that governs how much it costs to retrieve impounded vehicles. When designed well, the tow truck job becomes one of the most socially engaging roles on the server because every call involves direct interaction with another player who needs help with their broken or illegally parked vehicle.
Job Setup and Duty System
Start by defining the tow truck job within your framework's job system. Players should be able to clock in at a tow yard location, receive a company tow truck, and begin accepting dispatch calls. The duty system should track active tow drivers so the dispatch system knows who is available. When a player clocks in, spawn a flatbed truck from a predefined list of tow vehicle models and assign it to that player. When they clock out, the company vehicle should be returned or deleted. Store the player's on-duty status server-side so it persists correctly and can be queried by the dispatch system:
Config.TowJob = {
jobName = 'tow',
clockInLocation = vector3(409.09, -1622.57, 29.29),
vehicleSpawn = vector4(405.87, -1631.14, 29.29, 228.35),
towVehicles = {
{model = 'flatbed', label = 'Flatbed Truck', rank = 0},
{model = 'towtruck', label = 'Tow Truck (Hook)', rank = 1},
{model = 'towtruck2', label = 'Heavy Tow Truck', rank = 2},
},
impoundLot = vector3(409.59, -1637.87, 29.29),
impoundReturn = vector4(403.12, -1640.23, 29.29, 138.92),
payPerTow = {min = 350, max = 750},
payPerImpound = 500,
}
RegisterNetEvent('tow:server:clockIn', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
if Player.PlayerData.job.name ~= Config.TowJob.jobName then
TriggerClientEvent('QBCore:Notify', src, 'You are not a tow driver', 'error')
return
end
Player.Functions.SetJobDuty(true)
TowDrivers[src] = {
onDuty = true,
currentCall = nil,
vehicleNet = nil,
towedVehicle = nil,
}
TriggerClientEvent('QBCore:Notify', src, 'You are now on duty', 'success')
TriggerClientEvent('tow:client:spawnVehicle', src)
end)
Vehicle Attachment Mechanics
The core technical challenge of a tow system is attaching one vehicle to another in a way that looks realistic and behaves correctly during driving. FiveM provides the AttachEntityToEntity native for this purpose, but getting the offset positions right requires careful calibration for each tow truck model. The flatbed approach works best for most vehicles: the towed car is placed on top of the flatbed platform with appropriate offsets so it sits naturally on the bed. For hook-style tow trucks, the front wheels of the towed vehicle are lifted while the rear wheels remain on the ground. Each attachment style needs different offset vectors and rotation values. Test these values in-game by making small adjustments until the vehicle sits correctly without clipping through the truck:
function AttachVehicleToFlatbed(towTruck, targetVehicle)
local towModel = GetEntityModel(towTruck)
-- Offsets calibrated for the flatbed model
local offsets = {
['flatbed'] = {x = 0.0, y = -0.5, z = 1.1, rx = 0.0, ry = 0.0, rz = 0.0},
}
local offset = offsets['flatbed']
if not offset then return false end
-- Freeze target vehicle physics
SetEntityCollision(targetVehicle, false, false)
FreezeEntityPosition(targetVehicle, true)
-- Attach to flatbed
AttachEntityToEntity(
targetVehicle, towTruck,
0, -- bone index
offset.x, offset.y, offset.z,
offset.rx, offset.ry, offset.rz,
false, false, false, false, 0, true
)
-- Store attachment state
TriggerServerEvent('tow:server:vehicleAttached', VehToNet(towTruck), VehToNet(targetVehicle))
return true
end
function DetachVehicleFromFlatbed(towTruck, targetVehicle)
DetachEntity(targetVehicle, true, true)
SetEntityCollision(targetVehicle, true, true)
FreezeEntityPosition(targetVehicle, false)
-- Place vehicle on ground behind the truck
local truckCoords = GetEntityCoords(towTruck)
local truckHeading = GetEntityHeading(towTruck)
local behind = GetOffsetFromEntityInWorldCoords(towTruck, 0.0, -12.0, 0.0)
SetEntityCoords(targetVehicle, behind.x, behind.y, behind.z)
SetEntityHeading(targetVehicle, truckHeading)
SetVehicleOnGroundProperly(targetVehicle)
TriggerServerEvent('tow:server:vehicleDetached', VehToNet(towTruck))
end
An important detail is disabling collision on the attached vehicle to prevent physics glitches. When two vehicles have active collision and are attached, the game engine can produce violent shaking or launch the vehicles into the air. Freezing the target vehicle's position and disabling its collision while attached eliminates these issues entirely.
Dispatch and Tow Request System
The dispatch system connects players who need a tow with available tow truck drivers. Players can request a tow through their phone, through a mechanic shop NPC, or through roadside call boxes placed around the map. When a request comes in, the server checks for available tow drivers and routes the call to the nearest one. If no drivers are on duty, provide a fallback NPC tow service that charges a higher fee and teleports the vehicle to the impound lot after a delay. The dispatch notification should include the requester's location, vehicle model, and the reason for the tow such as breakdown, accident, or illegal parking. Give the tow driver the option to accept or decline the call, with declined calls being routed to the next nearest driver:
RegisterNetEvent('tow:server:requestTow', function(reason)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local playerCoords = GetEntityCoords(GetPlayerPed(src))
local vehicle = GetVehiclePedIsIn(GetPlayerPed(src), true)
local vehicleModel = vehicle ~= 0 and GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)) or 'Unknown'
-- Find nearest available tow driver
local nearestDriver = nil
local nearestDist = math.huge
for driverId, data in pairs(TowDrivers) do
if data.onDuty and not data.currentCall then
local driverCoords = GetEntityCoords(GetPlayerPed(driverId))
local dist = #(playerCoords - driverCoords)
if dist < nearestDist then
nearestDist = dist
nearestDriver = driverId
end
end
end
if nearestDriver then
TowDrivers[nearestDriver].currentCall = {
requesterId = src,
coords = playerCoords,
vehicle = vehicleModel,
reason = reason,
timestamp = os.time(),
}
TriggerClientEvent('tow:client:incomingCall', nearestDriver, {
coords = playerCoords,
vehicle = vehicleModel,
reason = reason,
playerName = Player.PlayerData.charinfo.firstname,
})
TriggerClientEvent('QBCore:Notify', src, 'Tow truck dispatched. ETA based on driver location.', 'success')
else
TriggerClientEvent('QBCore:Notify', src, 'No tow drivers available. NPC service will arrive shortly.', 'info')
StartNPCTowService(src, playerCoords, vehicle)
end
end)
Impound Lot Management
The impound lot is where towed and seized vehicles are stored until their owners retrieve them. Design the impound database to track which vehicles are currently impounded, who impounded them, when they were impounded, and the fee required for release. Police should be able to impound vehicles directly during traffic stops or after pursuits, with those impounds carrying higher fees or longer mandatory hold times. The impound lot interface should display a list of the player's impounded vehicles with details like the vehicle model, plate number, impound date, reason, and the release fee. Players pay the fee at the impound lot counter, and their vehicle spawns at a designated return point nearby:
CREATE TABLE IF NOT EXISTS impounded_vehicles (
id INT AUTO_INCREMENT PRIMARY KEY,
plate VARCHAR(10) NOT NULL,
citizenid VARCHAR(50) NOT NULL,
vehicle_data LONGTEXT NOT NULL,
impound_reason ENUM('tow', 'police', 'abandoned', 'parking') DEFAULT 'tow',
impound_fee INT DEFAULT 250,
impounded_by VARCHAR(50) DEFAULT NULL,
impounded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
hold_until TIMESTAMP DEFAULT NULL,
released_at TIMESTAMP DEFAULT NULL,
INDEX idx_plate (plate),
INDEX idx_citizen (citizenid),
INDEX idx_released (released_at)
);
-- Server-side: Impound a vehicle
RegisterNetEvent('tow:server:impoundVehicle', function(vehicleNet, reason)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local vehicle = NetworkGetEntityFromNetworkId(vehicleNet)
if not DoesEntityExist(vehicle) then return end
local plate = GetVehicleNumberPlateText(vehicle)
local vehicleData = QBCore.Functions.GetVehicleProperties(vehicle)
local fee = Config.ImpoundFees[reason] or 250
MySQL.insert('INSERT INTO impounded_vehicles (plate, citizenid, vehicle_data, impound_reason, impound_fee, impounded_by) VALUES (?, ?, ?, ?, ?, ?)', {
plate:trim(),
GetVehicleOwner(plate),
json.encode(vehicleData),
reason,
fee,
Player.PlayerData.citizenid,
})
DeleteEntity(vehicle)
local pay = math.random(Config.TowJob.payPerImpound - 100, Config.TowJob.payPerImpound + 100)
Player.Functions.AddMoney('bank', pay, 'tow-impound')
TriggerClientEvent('QBCore:Notify', src, 'Vehicle impounded. Earned $' .. pay, 'success')
end)
Automated Parking Enforcement
Add an automated system that detects vehicles parked in restricted zones and flags them for towing after a configurable grace period. Define restricted parking zones around fire hydrants, bus stops, police stations, and hospital emergency entrances. When a player parks in a restricted zone and walks away, start a timer. If the vehicle remains unattended after the grace period expires, generate a tow request and notify available tow drivers. This creates organic work for tow truck drivers without requiring players to manually call for service. The system should only flag player-owned vehicles because NPC vehicles are managed by the game engine and despawn naturally. Include a notification to the vehicle owner that their car has been flagged for towing, giving them a chance to return and move it before the tow truck arrives.
Revenue and Progression
Structure the tow job with a progression system that rewards consistent work. New tow drivers start at rank zero with access to the basic flatbed truck and earn a base rate per completed tow. As they accumulate completed tows, they rank up and unlock heavier tow vehicles capable of handling larger vehicles like trucks and buses, access to police impound contracts that pay more, and a personal tow truck that they can customize. Track statistics like total tows completed, distance driven while towing, and average response time to dispatch calls. Display these stats in a job dashboard accessible through an NPC or phone app. Consider implementing a reputation system where tow drivers who consistently respond quickly and complete jobs without damaging vehicles earn bonus multipliers on their pay, while drivers who ignore calls or damage vehicles during towing face pay penalties. This creates a natural quality incentive that benefits the entire server community by ensuring tow services are reliable and professional.
Handling Edge Cases and Networking
Tow systems have several tricky edge cases that need careful handling. When a tow driver disconnects while towing a vehicle, the server should detect the disconnection and safely detach and ground the towed vehicle so it does not float in the air or fall through the map. If the vehicle owner logs in while their car is attached to a tow truck, they should see their vehicle marked as being towed in their vehicle list with information about the tow driver. Handle vehicle entity ownership carefully because the attachment native only works reliably when the attaching client has ownership of both entities. Use NetworkRequestControlOfEntity to ensure the tow driver's client controls both vehicles before attempting the attachment. For multiplayer synchronization, broadcast the attachment state to all nearby players so they see the towed vehicle in the correct position on their screens. State bags work well for this because they automatically replicate to all clients in range of the entity.