Developer
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 handleExcludeFromPersistence(plateOrNetId, reason?): Alias ofDoNotSaveRemoveExclusion(plate): Clear an exclusionIsExcludedFromPersistence(plate): Returnstrueif excludedLockVehicle(plate): Temporarily block persistence (during towing, mechanic work). Auto-expires after 5 minutesUnlockVehicle(plate): Release a lockIsVehicleLocked(plate): Returnstrueif locked
Vehicle Control
PurgeVehicle(plateOrNetId, keepInWorld?): Remove a vehicle from persistence. Despawns it unlesskeepInWorldistrue. Returnsfalseand does nothing while someone is in the driver seat, unlesskeepInWorldis setUpdateVehicle(plateOrNetId): Force a save and refresh the vehicle's stored properties from its owner clientClaimVehicle(plateOrNetId, src): Register a vehicle as persistent and owned by the playersrcTransferPersistedVehicle(plate, newOwner): Reassign a persisted vehicle to another owner identifierPurgeOwner(ownerId): Remove every persisted vehicle belonging to an identifier. Returns the count removedSetVehiclePlate(plateOrNetId, newPlate): Move persistence to a new plate after a plate changeRemovePersistedVehicle(plate): Drop a vehicle from persistence, keeping it in the worldSetVehicleProtected(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.
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 stateIsPersistencePaused(): Returns the current pause state
Maintenance
RunCleanup(): Run theConfig.Cleanupsweep once, outside its schedule. Returns the number of vehicles removed
Query
IsVehiclePersisted(plate): Returnstrueif the plate is persistedGetWorldVehicles(): Returns every tracked vehicleGetVehicleStatus(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 positionGetVehiclesCoords(plates): Returns a map of plate to coordinatesGetVehicleModelsByPlates(plates): returns a map of plate to{ model }for tracked vehicles. Plates that are not tracked are left outGetVehiclesByFlag(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.
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. Returnstruewhen 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:
1shared_script '@ml_vehicle_persistence/deletehook.lua'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
1function OpenServer.CanPersist(src, vehicle, plate, owner)
2 return nil
3endOwnership 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
1function OpenServer.CanClaim(src, vehicle, plate)
2 return nil
3endGate before a world vehicle is claimed by its first driver. Return false to block the claim, for example to require a hotwire item.
FetchOwnedPlates
1function OpenServer.FetchOwnedPlates()
2 return {}
3endReturns an array of plate strings from the ownership database. Claiming never touches these plates. The default reads player_vehicles or owned_vehicles.
IsPlateOwned
1function OpenServer.IsPlateOwned(plate)
2 return false
3endAuthoritative 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
1function OpenServer.FetchGaragedVehicles(src, owner)
2 return {}
3endVehicles 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
1function OpenServer.OnVehicleRestored(plate, owner)
2endFires 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
1function OpenServer.ImpoundVehicle(plate, owner, fee, impoundLot)
2endOrphan 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
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) endOnVehicleRemoved 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:
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:
1function OpenClient.NotifyStored(plate)
2 TriggerServerEvent('ml_vehicle_persistence:server:vehicleStored', plate)
3endAdmin 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
1local netId = NetworkGetNetworkIdFromEntity(vehicle)
2exports.ml_vehicle_persistence:DoNotSave(netId, 'mission')1exports.ml_vehicle_persistence:UpdateVehicle(plate)1exports.ml_vehicle_persistence:ClaimVehicle(netId, src)