Developer

6 min readUpdated 2 weeks ago

Overview

Other resources can coordinate persistence through server exports and the open/server.lua hooks. The open/ folder holds ownership and garage-state logic that can be adapted for the server framework.

Server Exports

Coordination

  • DoNotSave(plateOrNetId, reason?): Exclude a vehicle from persistence and remove it if already tracked. Accepts a plate, a network id, or an entity handle
  • ExcludeFromPersistence(plateOrNetId, reason?): Alias of DoNotSave
  • RemoveExclusion(plate): Clear an exclusion
  • IsExcludedFromPersistence(plate): Returns true if excluded
  • LockVehicle(plate): Temporarily block persistence (during towing, mechanic work). Auto-expires after 5 minutes
  • UnlockVehicle(plate): Release a lock
  • IsVehicleLocked(plate): Returns true if locked

Vehicle Control

  • PurgeVehicle(plateOrNetId, keepInWorld?): Remove a vehicle from persistence. Despawns it unless keepInWorld is true. Returns false and does nothing while someone is in the driver seat, unless keepInWorld is set
  • UpdateVehicle(plateOrNetId): Force a save and refresh the vehicle's stored properties from its owner client
  • ClaimVehicle(plateOrNetId, src): Register a vehicle as persistent and owned by the player src
  • TransferPersistedVehicle(plate, newOwner): Reassign a persisted vehicle to another owner identifier
  • PurgeOwner(ownerId): Remove every persisted vehicle belonging to an identifier. Returns the count removed
  • SetVehiclePlate(plateOrNetId, newPlate): Move persistence to a new plate after a plate change
  • RemovePersistedVehicle(plate): Drop a vehicle from persistence, keeping it in the world
  • SetVehicleProtected(plate, state): Mark a tracked vehicle as exempt from the slot count, cleanup, decay and orphan removal. Returns false if the plate is not tracked.
  • ClearBaseLink(plate): Remove a tracked vehicle's saved base flag link.
lua
1exports.ml_vehicle_persistence:SetVehicleProtected(plate, true)
2exports.ml_vehicle_persistence:ClearBaseLink(plate)

Pause

  • PausePersistence(state): Pause or resume all saving and claiming. Returns the new state
  • IsPersistencePaused(): Returns the current pause state

Maintenance

  • RunCleanup(): Run the Config.Cleanup sweep once, outside its schedule. Returns the number of vehicles removed

Query

  • IsVehiclePersisted(plate): Returns true if the plate is persisted
  • GetWorldVehicles(): Returns every tracked vehicle
  • GetVehicleStatus(plate): Returns { isPersisted, isExcluded, isLocked, exclusion, lock, vehicle }
  • GetVehicleCoords(plate): Returns a persisted vehicle's live coordinates when available, or its saved coordinates otherwise. GPS tampering does not replace these with the owner's frozen GPS position
  • GetVehiclesCoords(plates): Returns a map of plate to coordinates
  • GetVehicleModelsByPlates(plates): returns a map of plate to { model } for tracked vehicles. Plates that are not tracked are left out
  • GetVehiclesByFlag(flagId): Return tracked vehicles linked to a base flag.
  • GetVehiclesByOwner(owner): Return tracked vehicles for an owner identifier.
  • GetVehiclesInArea(coords, radius): Return tracked vehicles within the radius of a vector3 or coordinate table.
  • CanStoreVehicle(plate): Return false, 'raid' when the vehicle is linked to a base under raid and storing is blocked; otherwise true.
lua
1local byOwner = exports.ml_vehicle_persistence:GetVehiclesByOwner(owner)
2local nearby = exports.ml_vehicle_persistence:GetVehiclesInArea(coords, 100.0)
3local canStore, reason = exports.ml_vehicle_persistence:CanStoreVehicle(plate)

Client Exports

  • PurgeVehicle(plateOrNetId): Drop a vehicle from persistence and delete it from the client side. Used by resources that delete vehicles on the client. Returns true when the request was accepted

Deleting Vehicles From Another Resource

With Config.RespawnOnExternalDelete = false (the default) nothing is needed: a plain DeleteEntity or an admin /dv drops the vehicle from persistence like every other script expects.

With Config.RespawnOnExternalDelete = true every delete is undone by the respawn engine unless it is routed through the script. Either call the PurgeVehicle export, or add the shipped hook as the first script line of the deleting resource:

fxmanifest.lua
1shared_script '@ml_vehicle_persistence/deletehook.lua'
First line

The hook replaces DeleteEntity and DeleteVehicle on the client, and DeleteEntity on the server. A resource that caches them in a local before the hook loads bypasses it, so the shared_script line must come before every other script entry.

The request is validated server-side: a tracked vehicle is only removed by an admin or by its own owner, and a locked vehicle is never touched.

Server Handlers

Located in open/server.lua.

CanPersist

open/server.lua
1function OpenServer.CanPersist(src, vehicle, plate, owner)
2    return nil
3end

Ownership gate before a vehicle becomes persistent. Return false to block, true to force-allow for custom ownership systems, nil for the default owned-vehicles check.

CanClaim

open/server.lua
1function OpenServer.CanClaim(src, vehicle, plate)
2    return nil
3end

Gate before a world vehicle is claimed by its first driver. Return false to block the claim, for example to require a hotwire item.

FetchOwnedPlates

open/server.lua
1function OpenServer.FetchOwnedPlates()
2    return {}
3end

Returns an array of plate strings from the ownership database. Claiming never touches these plates. The default reads player_vehicles or owned_vehicles.

IsPlateOwned

open/server.lua
1function OpenServer.IsPlateOwned(plate)
2    return false
3end

Authoritative single-plate check, consulted when a plate is missing from the cached registry, such as a car bought seconds earlier. Return true if the plate belongs to a player.

FetchGaragedVehicles

open/server.lua
1function OpenServer.FetchGaragedVehicles(src, owner)
2    return {}
3end

Vehicles the player owns that are sitting in a garage right now, so the GPS item shows them as stored instead of a dead waypoint. Return an array of { plate, model?, garage? }. The garage value is shown to the player as-is, so raw ids can be mapped to friendly names here. The default reads player_vehicles with state = 1.

OnVehicleRestored

open/server.lua
1function OpenServer.OnVehicleRestored(plate, owner)
2end

Fires for every vehicle put back into the world after a restart. The default updates player_vehicles on Qbox/QBCore. Adapt the hook for ESX or a custom garage.

ImpoundVehicle

open/server.lua
1function OpenServer.ImpoundVehicle(plate, owner, fee, impoundLot)
2end

Orphan cleanup with action = 'impound' routes database-owned vehicles here. Return true only after the impound succeeds; anything else keeps the vehicle in persistence. The default updates player_vehicles on Qbox/QBCore; adapt the hook for ESX or a custom impound. Claimed World Vehicles stay in persistence with this action.

Lifecycle Hooks

open/server.lua
1function OpenServer.OnVehiclePersisted(plate, owner) end
2function OpenServer.OnVehicleClaimed(plate, owner, src) end
3function OpenServer.OnVehicleRemoved(plate, owner, reason) end
4function OpenServer.OnGpsTampered(plate, owner, src) end
5function OpenServer.OnGpsInstalled(plate, owner, src) end

OnVehicleRemoved reports why a vehicle left persistence, including stored, excluded, destroyed, impounded, orphan_deleted, owner_purged, cleanup, admin_remove, admin_clear, deleted_by_client_hook, entity_removed, and resource-triggered removal.

OnGpsTampered fires when a thief rips a vehicle's GPS out with the scanner item; OnGpsInstalled fires when the owner restores live tracking with a module.

Server Events

Local server events for your own resources:

your_script/server.lua
1AddEventHandler('ml_vehicle_persistence:vehiclePersisted', function(plate, rawPlate, owner) end)
2AddEventHandler('ml_vehicle_persistence:vehicleClaimed', function(plate, rawPlate, owner, src) end)
3AddEventHandler('ml_vehicle_persistence:vehicleRemoved', function(plate, rawPlate, owner, reason) end)
4AddEventHandler('ml_vehicle_persistence:vehicleRestored', function(plate, rawPlate, owner, netId) end)
5AddEventHandler('ml_vehicle_persistence:vehiclePlateChanged', function(oldPlate, newPlate, rawPlate, owner) end)

Client Handlers

Located in open/client.lua. These listen for garage store events and drop the stored vehicle from persistence. Add your garage's event here:

open/client.lua
1function OpenClient.NotifyStored(plate)
2    TriggerServerEvent('ml_vehicle_persistence:server:vehicleStored', plate)
3end

Admin Integrations

Provider tables in open/integrations/ add rows and actions to vehicle inspection. Built-in providers check whether their resource is running. To add another provider, copy open/integrations/example.lua, remove its disabled guard and adapt its resource, rows and actions.

Examples

lua
1local netId = NetworkGetNetworkIdFromEntity(vehicle)
2exports.ml_vehicle_persistence:DoNotSave(netId, 'mission')
lua
1exports.ml_vehicle_persistence:UpdateVehicle(plate)
lua
1exports.ml_vehicle_persistence:ClaimVehicle(netId, src)
Vehicle Persistence Developer, FiveM Docs | Micio Mods