# Developer > Developer API reference for ML Vehicle Crafting Category: VEHICLE CRAFTING · Source: https://miciomods.it/docs/ml-vehiclecraft-developer · Last updated: 2026-08-14 ## Overview Integration happens in two places. `open/client.lua` and `open/server.lua` hold the hooks, in plain Lua, for gating and the moment a vehicle is registered to its owner. The exports hand out a chassis or a part already stamped for a given vehicle, which is how a shop, a loot table or a crafting bench feeds the system. The identifier used everywhere is the vehicle's GTA spawn name, which is also its key in `shared/data/vehicles.lua`. ## Server exports Callable only by the resources listed in `Config.ChassisExportResources`. - `GiveChassis(src, vehKey)` - gives one chassis for that vehicle, stamped. Returns a boolean - `GiveParts(src, vehKey)` - gives the whole set of parts for that vehicle in the right amounts, four wheels, four brakes, as many doors and seats as the model has. Returns a boolean - `GivePart(src, partType, vehKey, count?)` - gives one part type. `partType` is a key of `Config.PartTypes`. `count` defaults to 1. Returns a boolean ```lua exports.ml_vehiclecraft:GiveChassis(src, 'elegy2') exports.ml_vehiclecraft:GiveParts(src, 'elegy2') exports.ml_vehiclecraft:GivePart(src, 'engine', 'elegy2', 1) ``` ## Client exports - `GetVehicleList()` - returns the catalogue as `{ { vehKey, label, tier }, ... }`, sorted by tier then label - `MLAdminOpen()` - opens the admin panel, subject to the same permission check as the command ## Metadata With `Config.ChassisMode = 'metadata'` and `Config.Parts.modelLock` on, chassis and parts are generic items stamped with the vehicle they belong to. Any system that writes the item itself has to write the same metadata. **Chassis**, item `vc_chassis`: ```lua metadata = { model = 'elegy2', label = 'Elegy RH8 Chassis', } ``` `model` is required and decides what gets built. `label` is cosmetic and only changes the name shown in the inventory; the exports fill it from the catalogue. **Part**, items `vc_engine`, `vc_transmission`, `vc_brakes`, `vc_wheel`, `vc_door`, `vc_seat`, `vc_bonnet`, `vc_boot`, `vc_exhaust`: ```lua metadata = { model = 'elegy2', quality = 90, } ``` `model` is required only while `Config.Parts.modelLock` is on, `quality` only while `Config.Parts.quality` is on. ## Feeding the parts economy The script does not sell, spawn or loot parts. Pick whichever of these matches the server. **A shop that sells chassis and parts** Any shop that can run a server callback on purchase. Stamp through the export so the metadata is always right: **server/your_shop.lua** ```lua RegisterNetEvent('yourshop:bought', function(product) local src = source if product == 'chassis_emperor' then exports.ml_vehiclecraft:GiveChassis(src, 'emperor') elseif product == 'engine_emperor' then exports.ml_vehiclecraft:GivePart(src, 'engine', 'emperor', 1) end end) ``` Add your shop resource to `Config.ChassisExportResources` or the export refuses the call. **A crafting recipe that outputs a part** If the crafting system can attach metadata to a recipe output, set it directly: ```lua output = { item = 'vc_engine', count = 1, metadata = { model = 'elegy2' }, } ``` If it cannot, call the export in the recipe's give step instead. `ml_crafting` is already allowed in `Config.ChassisExportResources` and its recipe editor has a vehicle picker that fills the metadata for you. **A loot table that drops a random part** Drop this helper in your own server file and call it from the loot roll: **server/your_loot.lua** ```lua local PART_TYPES = { 'wheel', 'door', 'seat', 'exhaust', 'bonnet', 'boot', 'brakes', 'transmission', 'engine' } local VEHICLES = { 'emperor', 'regina', 'blista', 'rebel', 'bison' } function GiveRandomVehiclePart(src) local partType = PART_TYPES[math.random(#PART_TYPES)] local vehKey = VEHICLES[math.random(#VEHICLES)] return exports.ml_vehiclecraft:GivePart(src, partType, vehKey, 1) end ``` Weight the tables to taste. Engines and transmissions are the expensive slots, wheels and panels the common ones. **Anything else, through Bridge** When the item is written directly, the metadata has to be written with it: ```lua Bridge.GiveItem(src, 'vc_chassis', 1, { model = 'elegy2', label = 'Elegy RH8 Chassis' }) Bridge.GiveItem(src, 'vc_engine', 1, { model = 'elegy2', quality = 90 }) ``` Or straight through the inventory: ```lua exports.ox_inventory:AddItem(src, 'vc_chassis', 1, { model = 'elegy2' }) ``` > **TIP:** Model lock off means no metadata at all > > With `Config.Parts.modelLock = false` a part fits any vehicle, so a plain `/giveitem vc_engine` is enough and none of the above is needed for parts. The chassis still carries its vehicle unless `Config.ChassisMode` is set to `'items'`. ## Server handlers Located in `open/server.lua`. ### CanStartBuild **open/server.lua** ```lua function OpenServer.CanStartBuild(src, vehKey) return true end ``` Runs before the item and blueprint checks. Return `false` to block the project. ### BeforeFinalize **open/server.lua** ```lua function OpenServer.BeforeFinalize(src, build) return true end ``` Runs when a completed project is about to become a vehicle. Return `false` to stop it. The project stays intact. ### OnFinalized **open/server.lua** ```lua function OpenServer.OnFinalized(src, build, vehicle, plate) end ``` Registers the finished vehicle with the garage system. The shipped default writes a Qbox owned vehicle; QBCore and ESX versions are in the file, commented. Only runs while `Config.Output.mode` is `'owned'`. ### PlateExists **open/server.lua** ```lua function OpenServer.PlateExists(plate) return false end ``` Returns true when a plate already belongs to an owned vehicle, so the generator re-rolls. The default queries `player_vehicles` on Qbox and QBCore and `owned_vehicles` on ESX. It is also what stops a player's own vehicle from being salvaged. ### GetSkillLevel **open/server.lua** ```lua function OpenServer.GetSkillLevel(src, category) return 0 end ``` Player level in a skill category, used by `Config.SkillGate`. Wired to `ml_skills` by default. ### CanJobAction **open/server.lua** ```lua function OpenServer.CanJobAction(src, action) return true end ``` Job check for `build`, `work` and `salvage`, used by `Config.JobGate`. ### HasUnlock **open/server.lua** ```lua function OpenServer.HasUnlock(src, vehKey) return false end ``` Whether a player owns the unlock for a donor-only vehicle. The default reads the unlocks table and an optional per-vehicle ace. ### ChargeForPremium **open/server.lua** ```lua function OpenServer.ChargeForPremium(src, action, cost) return true end ``` Spends the paid uses. Return `false` to refuse the action. `RefundPremium(src, action, cost)` is its mirror and runs when an action fails after being charged. ## Client handlers Located in `open/client.lua`. > **INFO:** Notifications > > Notifications are sent through `ml_bridge`. Set `Config.Notify` in `ml_bridge/config.lua` to pick the system: auto, ox, qb, esx, wasabi, okok, mythic, pnotify, tnotify, brutal, lation, r_notify, fl, zsx. ### BeforeOpenPanel **open/client.lua** ```lua function OpenClient.BeforeOpenPanel(buildId) return true end ``` Runs before the build panel opens. Return `false` to block it. ### OnBuildFinalized **open/client.lua** ```lua function OpenClient.OnBuildFinalized(vehicle, vehKey) end ``` Runs on the builder's client once the finished vehicle exists, before the camera showcase. ## Commands Every command also runs from the server console, where the permission check is skipped. - `/vehiclecraft` - opens the admin panel. Admin tier - `vc_givechassis ` - GiveItems tier - `vc_giveparts ` - GiveItems tier - `vc_grantunlock ` - Premium tier - `vc_revokeunlock ` - Premium tier - `vc_givecredit [amount]` - Premium tier, amount defaults to 1 and is capped at 999 `/vcredeem` is a player command, not an admin one, and only exists while the paid layer is on. ## Store integration `server/tebex_integration.lua` registers the packages from `Config.Premium.packages` with `ml_tebex` and exposes two exports it calls back: - `OnTebexPackage` - grants uses, or an unlock plus its chassis. Works for offline purchases: the balance and the unlocks live in the database, so a player who bought from the web store gets them on next login - `OnTebexRefund` - revokes a chargeback. Removes the unlock and claws back unused uses, and reports whether the revoke was clean, partial or impossible Both are ignored when `ml_tebex` is not running. `/vcredeem` opens the player screen: their balance, their unlocks and a box to enter a purchase id. The box hands the id to `ml_tebex`, which verifies it against the store and delivers. Without `ml_tebex` the box answers that redemption is unavailable, and uses are granted by the admin panel instead.