How Integrations Work#
Vehicle Keys reaches your framework and inventory through small bridge files, picked at start by looking at what is already running. Nothing else in the script names a framework, so the same features work everywhere a bridge exists.
Layer | Supported | Without it |
|---|---|---|
Framework |
| Standalone: keys work on ox_inventory only, and there are no owned vehicles — no owner access, recovery, admin list, saved plate swaps or paid copy shop. |
Inventory |
| No keys can be stored. The one layer you cannot skip. |
Target |
| Prompts and a key press. GPS trackers cannot be removed. |
Text prompts |
| ox_lib, which is a dependency anyway. |
Dispatch |
| Police jobs get a notification and a flashing blip. |
Sounds |
| GTA's own door sounds only. |
Garages |
| Garages hand out keys themselves, with one call. |
Database |
| Required. |
Detection only sees resources that are already started. Anything you want found has to be above
ensure qs-vehiclekeysinserver.cfg.
Frameworks#
Qbox is detected from qbx_core and served by the QBCore bridge, through the qb-core compatibility Qbox provides itself.
ESX | QBCore · Qbox | Standalone | |
|---|---|---|---|
Player id |
|
| The player's first identifier |
Owned vehicles |
|
| None |
Admins | Groups | Permissions | The |
Copy shop pays from |
|
| Nothing — paid services are refused |
Jobs | The ESX job | The QBCore job | Always |
Usable items |
|
| ox_inventory's |
Scripts written for qb-vehiclekeys and qbx_vehiclekeys keep working through a compatibility layer that is always on — Replacing Other Key Scripts lists every event and export it answers.
Inventories#
Every key, plate and tracker is an item, and a key is only as good as the metadata the inventory keeps on it.
Inventory | Reached through | Extra |
|---|---|---|
| Its own exports, directly | A plate created without a number gets one on creation. Plate swaps move ox's trunks and gloveboxes. Plates, GPS and trackers are used through |
| The framework's item functions | Plate swaps move qs-inventory's trunks and gloveboxes. |
| The framework's item functions | — |
ESX's default inventory | — | Not supported: it keeps no item metadata. |
On ox_inventory the script registers six client exports for client.export: useKey, useLockpick, useAdvancedLockpick, usePlate, useGps and useTracker. Inventory Items shows which item needs which.
Target#
Config.UseTarget = true turns on target options through ox_target or qb-target, whichever is running:
Where | Option | Also needs |
|---|---|---|
Copy shop | Open key copy menu |
|
Nearest car | Run vehicle diagnostic · Measure window tint |
|
Tracked cars | Remove GPS tracker |
|
Without targeting, the copy shop and the recovery clerk show a prompt and open with E, and the diagnostic tablet has /diagnostic. Removing a tracker has no fallback — it needs ox_target.
Text Prompts and Notifications#
Config.TextUI picks how prompts — the hotwire hint, the clerk's Press E — are drawn:
Value | Look |
|---|---|
| ox_lib's text UI at the left of the screen. The default. |
| Quasar's 3D text. Falls back to |
| Plain GTA text floating over the spot. |
Notifications go through ox_lib, titled Vehicle Keys. Every message passes through one function in custom/client.lua — change it to use another system:
-- custom/client.lua
function Notification(msg, kind) -- kind: 'info' | 'success' | 'error'
exports['okokNotify']:Alert('Vehicle Keys', msg, 5000, kind)
endPolice Dispatch#
When a hotwire, a lockpick or a robbery rolls its DispatchChance, the alert leaves through Config.Dispatch:
Value | What happens |
|---|---|
| Every player with a job in |
| A dispatch call to the police jobs, with the code, snippet and blip from |
| Raises the server event |
| An origen_police alert to the police jobs, of type |
Config.Dispatch = 'default' -- default | qs-dispatch | qs-smartphone | origen
Config.DispatchSettings = {
Default = {
Blip = { Sprite = 161, Colour = 1, Scale = 1.2, DurationMs = 20000 }
},
QsDispatch = {
Code = '10-42',
Snippet = 'Vehicle Theft',
Blip = { Sprite = 490, Scale = 1.5, Colour = 1, DurationMs = 3 * 60 * 1000 }
},
Origen = {
Type = 'GENERAL'
}
}Another dispatch system? server/custom/dispatch/main.lua is open: add a branch to DispatchVehicleKeys that calls your resource with the title, message and coordinates it receives, and set Config.Dispatch to the name you chose.
Sounds#
The fob beep and the anchor are played through interact-sound, so the files live in that resource, not in this one. Put them in interact-sound's client/html/sounds/ folder, named:
File | Played when |
|---|---|
| A car is locked or unlocked. The name comes from |
| The anchor is lowered. |
| The anchor is raised. |
Without interact-sound, GTA's own door sounds still play — unless Config.SoundsDisableDefault mutes them.
Garages, Dealerships and Jobs#
A car that another script spawns for a player — out of a garage, off a dealership forecourt, from a job depot — needs its key handed over by that script. One call does it, on either side:
-- after spawning the car for this player
local plate = GetVehicleNumberPlateText(vehicle)
exports['qs-vehiclekeys']:GiveServerKeys(source, plate, 'sultan', false)
-- a job van returned
exports['qs-vehiclekeys']:RemoveServerKeys(source, plate, 'sultan')local plate = GetVehicleNumberPlateText(vehicle)
local model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle))
exports['qs-vehiclekeys']:GiveKeys(plate, model, false)
-- a job van returned
exports['qs-vehiclekeys']:RemoveKeys(plate, model)The last argument, false, skips the key when the player already holds one for that plate — take the car out every day and the inventory still holds a single key. The client export also lets the player drive straight away, without waiting for the item to land, so nobody is asked to hotwire a car they were just given.
Plate changes#
When a plate is swapped, Vehicle Keys updates qs-advancedgarages itself, then fires a server event any other script can listen to:
AddEventHandler('vehiclekeys:server:plateChanged', function(oldPlate, newPlate)
-- update your own tables here
end)Lock state#
The server remembers each car's last lock state by plate. A garage putting a car back out can restore it:
local state = lib.callback.await('vehiclekeys:server:getVehicleLockStateByPlate', false, plate)
if state then
SetVehicleDoorsLocked(vehicle, state)
endEditing the Bridges#
These parts ship open, so you can change how the script talks to the rest of your server:
Path | Holds |
|---|---|
| Every setting. |
| Every string, in 41 languages. |
| The framework bridges: players, jobs, items, admins, owned vehicles and plates. |
| Notifications, text prompts and the |
| The dispatch blip, the ox_inventory item exports and the target zones. |
| The dispatch providers. |
| The list of functions a framework bridge fills in. |
A server bridge provides these functions. To support a framework of your own, add it to the frameworks table at the top of config/main.lua, pointing at a folder name, then copy the closest bridge into custom/<that name>/ and change the calls inside.
Functions | Used for |
|---|---|
| Who the player is, their job, and whether they are staff. |
| Keys, lockpicks, plates and trackers. |
| Owner access, key recovery and cleaning keys. |
| Plate swaps and fresh plate numbers. |
| The admin panel. |
Languages#
41 translations ship in locales/: Arabic (ar), Bulgarian (bg), Bengali (bn), Catalan (ca), Czech (cs), Danish (da), German (de), Greek (el), English (en), Spanish (es), Estonian (et), Persian (fa), Finnish (fi), French (fr), Hebrew (he), Hindi (hi), Croatian (hr), Hungarian (hu), Indonesian (id), Italian (it), Japanese (ja), Korean (ko), Lithuanian (lt), Latvian (lv), Malay (ms), Dutch (nl), Norwegian (no), Polish (pl), Portuguese (pt), Romanian (ro), Russian (ru), Slovak (sk), Slovenian (sl), Swedish (sv), Swahili (sw), Thai (th), Turkish (tr), Ukrainian (uk), Urdu (ur), Vietnamese (vi) and Chinese (zh).
Set Config.Locale to the code. To add a language, copy en.json to the new code, translate it and set the locale — anything left out falls back to English, and the interface uses the same file.



