Installation
This guide is written for Qbox (qbx_core + ox_*). Differences for QBCore and ESX are noted in the relevant steps.
- Files
- Database
- server.cfg
- Job definition
- Items (optional)
- Admin permissions
- HQ location
- Recording job spots
- Integrations
- Installation checklist
- Troubleshooting
1. Files
Copy the folder somewhere like resources/[nullsect]/ns-locksmith. Layout:
ns-locksmith/
├── fxmanifest.lua
├── config.lua ← all settings (CONFIGURATION.md)
├── bridge/ ← framework / inventory / target / dispatch / UI bridges
├── shared/ ← locale, constants, customer pool
├── server/ ← authoritative game logic
├── client/ ← scene, NPCs, interaction, NUI bridge
├── nui/ ← tablet + minigame UI (offline fonts included)
├── sql/locksmith.sql ← schema
└── data/locations.json ← job spots recorded in-game (created on the first recording if missing)
Escrow: the resource is protected with Cfx.re asset escrow.
config.lua,shared/locale.luaandshared/customers.luaare open for editing; all other Lua files are encrypted. The asset is tied to the Cfx.re account that bought it (Keymaster), so run it on a server whose license key belongs to that account.
data/locations.jsonships with 110 ready job spots (52 houses, 40 vehicles, 18 businesses) and holds your own recordings too. Do not overwrite this file when updating. To start from zero, set its contents to{"locations":[],"disabledDefaults":[]}.
2. Database
Automatic (recommended): Config.Database.AutoMigrate = true is enabled by default. sql/locksmith.sql runs on every start (CREATE TABLE IF NOT EXISTS, safe to repeat) and any missing indexes are added. The added index ... line only appears when upgrading from an older version.
Manual: If you set AutoMigrate = false, import sql/locksmith.sql once with HeidiSQL or phpMyAdmin.
Tables created:
| Table | Contents |
|---|---|
locksmith_players | level, XP, reputation, career path, licence, settings (JSON) |
locksmith_stats | job counts, earnings, times |
locksmith_equipment | tool durability and upgrade level |
locksmith_skills · locksmith_achievements | skill ranks, unlocked achievements |
locksmith_job_history | every completed / cancelled job (deleted after HistoryRetentionDays) |
locksmith_contracts · locksmith_business_rep | daily / business / master contracts, customer relationships |
locksmith_audit_log | admin actions, purchases, security warnings (deleted after AuditRetentionDays) |
Tables are keyed to the character ID (citizenid / ESX identifier). When a profile is deleted, its linked rows are deleted too (FK cascade); the audit log is the exception and its records are kept.
3. server.cfg
Dependencies must start before the locksmith resource:
ensure oxmysql
ensure ox_lib
ensure qbx_core # or qb-core / es_extended
ensure ox_target # or qb-target
ensure ox_inventory # optional
ensure ox_doorlock # optional
ensure ps-dispatch # optional (or cd_dispatch)
ensure ns-locksmith
add_ace group.admin locksmith.admin allow
Framework, inventory, target and dispatch are detected automatically. If detection gets it wrong, set Config.Framework / Inventory / Target at the top of config.lua and Config.Police.Dispatch manually.
4. Job definition (optional)
By default (
Config.RequireJob = false) no job is needed: the tablet item is the access key (see §5). Skip this section unless you setConfig.RequireJob = true.
The script identifies the job by Config.JobName (default locksmith). The job grade is not the locksmith level. Level progresses with XP; grade only affects salary and boss access.
Qbox — qbx_core/shared/jobs.lua
['locksmith'] = {
label = 'Locksmith',
defaultDuty = true,
offDutyPay = false,
grades = {
[0] = { name = 'Apprentice', payment = 50 },
[1] = { name = 'Locksmith', payment = 75 },
[2] = { name = 'Senior', payment = 100 },
[3] = { name = 'Supervisor', payment = 125 },
[4] = { name = 'Manager', payment = 150 },
[5] = { name = 'Owner', payment = 200, isboss = true, bankAuth = true },
},
},
QBCore — qb-core/shared/jobs.lua
['locksmith'] = {
label = 'Locksmith',
defaultDuty = true,
offDutyPay = false,
grades = {
['0'] = { name = 'Apprentice', payment = 50 },
['1'] = { name = 'Locksmith', payment = 75 },
['2'] = { name = 'Senior', payment = 100 },
['3'] = { name = 'Supervisor', payment = 125 },
['4'] = { name = 'Manager', payment = 150 },
['5'] = { name = 'Owner', payment = 200, isboss = true },
},
},
ESX — SQL
INSERT INTO `jobs` (`name`, `label`) VALUES ('locksmith', 'Locksmith');
INSERT INTO `job_grades` (`job_name`, `grade`, `name`, `label`, `salary`, `skin_male`, `skin_female`) VALUES
('locksmith', 0, 'apprentice', 'Apprentice', 50, '{}', '{}'),
('locksmith', 1, 'employee', 'Locksmith', 75, '{}', '{}'),
('locksmith', 2, 'senior', 'Senior', 100, '{}', '{}'),
('locksmith', 3, 'supervisor', 'Supervisor', 125, '{}', '{}'),
('locksmith', 4, 'manager', 'Manager', 150, '{}', '{}'),
('locksmith', 5, 'boss', 'Owner', 200, '{}', '{}');
Other options:
- With
Config.RequireOnDuty = true, an off-duty locksmith cannot take jobs. This setting uses the qb / qbx duty flag. - With
Config.RequireJob = true, only players with the job can work. Combined withConfig.Items.RequireTablet = true, they need both the job and the tablet.
5. Items
The tablet item is required with the default settings (Config.Items.RequireTablet = true): players buy it at any HQ with features.shop (target option Buy locksmith tablet, price Config.Items.TabletPrice). While they carry it they are a locksmith: they get automatic calls, can use the job board and do lock work. Without it they are not. Using the item (or /lstablet) opens the tablet. The toolkit item is optional.
| Item | Use |
|---|---|
locksmith_tablet | Required (default). Makes the carrier a locksmith; opens the tablet when used |
locksmith_toolkit | Required to work if Config.Items.RequireToolkitItem = true. Sold in the HQ shop (Config.Shop.Items) |
Tools (lockpick set, drill, etc.) are not items. Their durability is stored in the locksmith database, so they behave the same with every inventory.
ox_inventory — ox_inventory/data/items.lua
['locksmith_tablet'] = {
label = 'Locksmith Tablet',
weight = 600,
stack = false,
close = true,
description = 'Work orders, workshop and career terminal',
},
['locksmith_toolkit'] = {
label = 'Locksmith Toolkit',
weight = 2500,
stack = false,
close = true,
description = 'Basic toolkit for lock jobs',
},
Add images as ox_inventory/web/images/locksmith_tablet.png and locksmith_toolkit.png. The package does not include images.
QBCore — qb-core/shared/items.lua
locksmith_tablet = { name = 'locksmith_tablet', label = 'Locksmith Tablet', weight = 600, type = 'item', image = 'locksmith_tablet.png', unique = true, useable = true, shouldClose = true, description = 'Work orders and career terminal' },
locksmith_toolkit = { name = 'locksmith_toolkit', label = 'Locksmith Toolkit', weight = 2500, type = 'item', image = 'locksmith_toolkit.png', unique = true, useable = false, shouldClose = true, description = 'Toolkit for lock jobs' },
The script registers the tablet's "use" behaviour itself (CreateUseableItem / RegisterUsableItem); you don't need to do anything else.
6. Admin permissions
Any one of the following is enough for the admin commands and /lslocations:
- ACE:
add_ace group.admin locksmith.admin allow(ACE name isConfig.Admin.Ace) - Framework admin: when
Config.Admin.AllowFrameworkAdmins = true:- on Qbox, the ACEs listed in
QBPermissions(admin,god) - on QBCore, the permissions with the same names
- on ESX,
ESXGroups
- on Qbox, the ACEs listed in
7. HQ location
The first entry in Config.Locations is the HQ. Skill resets, career path selection, licence renewal and licence reinstatement can only be done here (within 10 m). Even when started from the tablet, you need to be at the HQ.
Config.Locations = {
{
id = 'hq',
name = 'Locksmith HQ',
coords = vector3(170.73, -1799.37, 29.32), -- change to suit your map
size = vector3(1.6, 1.6, 2.0), -- target box
heading = 0.0,
blip = true,
jobOnly = true, -- target options only for locksmiths
features = { terminal = true, board = true, license = true, shop = true, repair = true },
van = vector4(160.52, -1807.43, 28.61, 50.0), -- service van bay (optional)
tutorial = vector4(165.20, -1793.10, 28.32, 320.0), -- apprentice training door (optional)
ped = { model = 's_m_m_autoshop_01', coords = vector4(170.19, -1799.48, 27.32, 140.0), scenario = 'WORLD_HUMAN_CLIPBOARD' }, -- HQ clerk (optional)
},
}
van is the service van bay: the rented van spawns there and must be parked there to be returned. Stand on an empty parking spot, type /lscoords and paste the value. Remove the line to disable van rental at that HQ.
tutorial is the apprentice training door: stand in front of a real door, facing it, and use /lscoords. Without it no training is offered.
ped is the HQ clerk: every HQ option is on this NPC. Stand where the clerk should stand, facing the customer side, and use /lscoords. Without it a plain target box at coords is used.
Other spots to set: the black market fence Config.BlackMarket.Broker.coords (with /lscoords). Black market drop points and spots are recorded in /lslocations (Black market tab); until then the example drops in config.lua are used.
To add another shop or workshop point, just add a new entry. For example, for a repair-only point, use features = { repair = true }.
To find coordinates, stand at the spot, type /lscoords and paste the value from your clipboard.
8. Recording job spots
The package ships 110 ready job spots in data/locations.json; Config.JobLocations in config.lua adds approximate default spots. To add, move or remove spots, use /lslocations (admin) in-game. It opens the Location Tool panel:
- Left: spot counts per kind, door type counts, and warnings for what is missing (for example no business spot, so business and safe jobs are off).
- Middle: every active spot with its distance to you, filters per kind, and a name search.
- Right: a small map with all spots and your position, and the details of the selected spot.
See where things spawn: turn on Show spots in the world (or type /lslocations view). Near spots you see a ghost of the customer's car and the customer NPC, the door area and a label. Spots within Config.LocationTool.ViewRange get a marker. Map blips adds a blip for every spot. Spots with a job running right now get no ghost.
New spot: pick Vehicle, House door or Business door. While you walk, a live ghost shows where the customer will stand and where the door is assumed. Press E to open the form, X to cancel.
- Vehicle: park where the customer's car should be and stay inside the vehicle.
- House / business door: stand in front of the door, back to the door, facing the street. The door is assumed to be
Config.Houses.DoorOffsetbehind you. Fill in the door type, the business type (business only), and optionally the ox_doorlock door name or ID.
Black market tab: record the black market spots the same way: Car to steal (sit in a car), Break-in door and Safe spot (on foot, back to the door) and Drop point (on foot or in a car; the circle shows the delivery area). Nobody waits at these spots. Until a kind has its own spots, the normal job spots are used (Config.BlackMarket.Spots); recorded drops replace the example drops in config.lua.
Selected spot actions: teleport, show in the world (with a waypoint), send a test call (accept with Y), edit the name and types, move it to where you stand now, delete. Spots with a running job cannot be edited or moved.
Recordings take effect immediately; no restart needed. Once you have recorded enough spots, set Config.Jobs.UseDefaultLocations = false to use only your own spots. Without ox_lib the panel still works; the old ox_lib menu is only a fallback when the NUI cannot open.
9. Integrations
| Resource | What it does | Setting |
|---|---|---|
| ox_doorlock | When a job at a door-linked spot succeeds, the door unlocks and relocks after RelockAfter seconds | Config.Houses.DoorlockIntegration, RelockAfter |
| ps-dispatch / cd_dispatch | Sends a call to police for alarms, suspicious attempts, fake owners, restricted zones and similar | Config.Police.Dispatch = 'auto' (ps > cd > built-in) |
| Built-in dispatch | Notification and temporary blip for on-duty police | 'builtin'; 'none' to disable |
| ox_lib | Menus, input, progress bars, confirmation dialogs; notifications with Config.Notify = 'ox_lib' | — |
Police jobs are read from Config.Police.Jobs (default police, bcso, sasp).
10. Installation checklist
| Step | Expected |
|---|---|
| Server start | [locksmith] ready (framework=qbx, inventory=ox). There should be no Modules not loaded error |
| Buy the tablet at HQ (target Buy locksmith tablet) | Money is taken, the item is added, profile loaded <citizenid>; players, stats and 3 equipment rows are created in the DB |
| Character without the tablet | Profile does not load and no calls arrive (normal) |
| Target the HQ clerk | Terminal, buy tablet, tool locker, van and training options appear (job board, workshop, career and licence are in the terminal) |
/lstablet | Tablet opens; home page shows level, licence and daily contracts |
/locksmithjob <id> vehicle | Call arrives, accept with Y, GPS, customer NPC |
| Finish the job | Receipt, money, XP; a locksmith_job_history row is added |
restart ns-locksmith | Profiles are saved and reloaded ~2 s later. Active jobs in progress are cancelled (jobs are kept in memory only) |
| Target a locksmith as a police character | Verify locksmith licence and Ask for work order options appear |
| New character with the tablet | Apprentice training? dialog; the checklist appears on the left and the training call comes in after opening the tablet |
| Rent the van at HQ (level 2) | Van spawns in the bay; Take tools opens the tool cabinet with the rear doors open |
| Tablet → Company (level 5, at HQ) | A company can be founded; the licence card shows its name |
| Fence at night (level 4) | Talk → a Channel section appears in the tablet |
11. Troubleshooting
Modules not loaded: ... / new feature didn't show up
FXServer caches fxmanifest.lua. After updates that add new files, type refresh in the console, then restart ns-locksmith.
No options at HQ
- Check that the target resource starts before the locksmith resource.
- With
jobOnly = true, only players with the locksmith job see them. - If
Config.RequireOnDutyis enabled, you need to go on duty.
profile_not_loaded / tablet won't open
- Check that the job name matches
Config.JobNameexactly. - Check the oxmysql connection and any SQL errors in the console.
No calls coming in
- Enable automatic calls with
/lsdispatch. - Your licence must be valid. Check the licence card on the tablet:
- if expired, renew it at the HQ;
- if suspended, it lifts when the suspension ends (if an admin set it with
/locksmithlicense … suspended, it has no end date and the admin must set it tovalid); - if revoked, get it reinstated at the HQ.
- You must not have an active job.
Minigame immediately returns "invalid"
- If the console shows a
minigame_too_fastwarning, the result arrived under the minimum time (bot / macro). - If legitimate players are hitting it, lower the
Config.Minigames.MinTimevalues.
Menus won't open, "ox_lib not found" warning Start ox_lib before the locksmith resource. While the NUI is open, the tablet and minigames work without ox_lib, but the fallback menus require ox_lib.
"invalid_request" on a vehicle job The client spawns the job vehicle. Attempts to register a different vehicle (a player vehicle or another job's vehicle) are deliberately rejected. If a vehicle of the same model is parked near the spot, move the spot slightly.
Worried about data loss
- The profile is saved when a job finishes, on level-up, and every
AutosaveInterval(300 s). - It is also saved when the player disconnects and when the resource stops.