Tutorial 2026-05-22

FiveM Tow Truck & Impound System

TDYSKY

TDYSKY

Founder & Lead Developer at Agency Scripts

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.

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.