Installation Guide
Step-by-step setup for oxide-baitcar — dependencies, SQL, per-framework item registration, and verification.
Step-by-step setup for oxide-baitcar.
Prerequisites
Required resources
| Resource | Purpose |
|---|---|
ox_lib | Callbacks, timers, UI helpers |
oxmysql | Database driver for the baitcar_events audit log |
o-link | Framework and system abstraction used by this resource |
Optional resources
| Resource | Purpose |
|---|---|
oxide-police | Dashcam clip recording uploads. Clips are stored in police_bodycam_recordings and surfaced in the MDT Video Storage tab. Without oxide-police running, the recorder mount is skipped and the F8 hotkey is a no-op. |
o-link dispatch adapter (ps-dispatch, cd_dispatch, oxide-dispatch, etc.) | Broadcasts the Bait Car Triggered alert when a suspect drives off. Without an adapter that satisfies dispatch.CreateAlert, the audit row still logs but no alert fires. |
o-link expectations
This resource depends on your server's o-link setup for:
- player identity and character lookup
- job and rank checks (
AllowedJobs) - notifications (officer and suspect toasts)
- inventory reads, item add / remove, and usable-item registration
framework.RegisterUsableItemfor the chip, the remote controller, and the removal tool (screwdriver)progressbar.Openfor the install and search animationsradial.Register/radial.AddItemfor the radial entry (dashboard mode only)dispatch.CreateAlertfor theft alertsmenu.Openandinput.Openfor the dashcam tunerloggerfor the structured audit / debug log
Installation
Place the resource
Place oxide-baitcar inside your server's resources folder.
Add startup order
Start dependencies before this resource:
ensure ox_lib
ensure oxmysql
ensure o-link
ensure oxide-police # optional, but required for clip recording
ensure oxide-baitcarIf you use a different dispatch resource, start its o-link adapter before oxide-baitcar as well.
Install the SQL table
Run sql/install.sql against your database. This creates the single audit table the resource writes to:
CREATE TABLE IF NOT EXISTS `baitcar_events` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`officer_charid` VARCHAR(60) NOT NULL,
`plate` VARCHAR(8) NOT NULL,
`model` VARCHAR(50) NULL,
`event_type` ENUM(...) NOT NULL,
`suspect_charid` VARCHAR(60) NULL,
`coords` JSON NULL,
`extra` JSON NULL,
`created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX `idx_officer` (`officer_charid`),
INDEX `idx_plate` (`plate`),
INDEX `idx_event_type` (`event_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;The event_type enum covers every action the resource records: deployed, lock, unlock, engine_off, engine_on, triggered, arrest, chip_removed, decommissioned, steering_off, steering_lock, steering_left, steering_right, speed_limit_on, speed_limit_off, strobe_on, strobe_off, alarm_on, alarm_off, horn.
Register the inventory items
oxide-baitcar uses three items by default. The item names below match the defaults in shared/config.lua (Config.ChipItem, Config.RemovalToolItem, Config.RemoteControllerItem) — if you change those values, register your custom names instead.
| Item name | Purpose | Image |
|---|---|---|
baitcar_chip | The chip an officer installs into a target vehicle. Consumed on install, returned to inventory if a suspect successfully removes it. | items/baitcar_chip.png |
screwdriver | Civilian removal tool (config default). Must be registered as usable. The civilian uses it from inventory while sitting in a vehicle to roll for chip removal; used outside a vehicle it silently no-ops. Most servers already ship a screwdriver — leave Config.RemovalToolItem = 'screwdriver' to reuse it. A screwdriver.png image ships in items/ if your inventory framework does not already provide one. | items/screwdriver.png |
baitcar_remote | Handheld remote controller. Required entry point for all bait-car controls when Config.UseRemoteController = true (the default). | items/baitcar_remote.png |
Add to qb-core/shared/items.lua:
['baitcar_chip'] = { name = 'baitcar_chip', label = 'Bait Chip', weight = 50, type = 'item', image = 'baitcar_chip.png', unique = false, useable = true, shouldClose = true, description = 'Police-issue chip. Install in a target vehicle to enable remote control.' },
['baitcar_remote'] = { name = 'baitcar_remote', label = 'Bait Car Remote', weight = 100, type = 'item', image = 'baitcar_remote.png', unique = true, useable = true, shouldClose = true, description = 'Handheld controller for police bait cars.' },
-- Most servers already define screwdriver; if not (note: useable = true so oxide-baitcar can bind its use handler):
['screwdriver'] = { name = 'screwdriver', label = 'Screwdriver', weight = 200, type = 'item', image = 'screwdriver.png', unique = false, useable = true, shouldClose = true, description = 'A flat-head screwdriver.' },Insert into the items table:
INSERT INTO `items` (`name`, `label`, `weight`) VALUES
('baitcar_chip', 'Bait Chip', 1),
('baitcar_remote', 'Bait Car Remote', 1),
('screwdriver', 'Screwdriver', 1);Register usable items on the server (for example in es_extended/server/main.lua or an esx_addoninventory config). oxide-baitcar registers its own usable handlers through o-link.framework.RegisterUsableItem, so no extra usable wiring is required as long as your ESX o-link adapter implements it.
Add to ox_inventory/data/items.lua:
['baitcar_chip'] = { label = 'Bait Chip', weight = 50, client = { image = 'baitcar_chip.png' } },
['baitcar_remote'] = { label = 'Bait Car Remote', weight = 100, client = { image = 'baitcar_remote.png' }, stack = false },
-- ox_inventory ships a screwdriver out of the box; reuse it.Copy item images
Copy the .png files from items/ into the image directory used by your inventory resource.
Common locations:
qb-inventory/html/images/ox_inventory/web/images/
The packaged image filenames are baitcar_chip.png, baitcar_remote.png, and screwdriver.png (only copy the latter if your inventory framework does not already ship a screwdriver image).
User interface (prebuilt)
The dashboard, remote controller, and dashcam recorder are prebuilt and already included in the resource. There is nothing to build or install — they work as soon as the resource starts.
Set up media uploads (FiveManage)
This only matters if you want dashcam clips to be saved. Bait car dashcam clips do not upload on their own — they go through oxide-police's recording pipeline. So two things must be true for clips to save:
oxide-policeis installed and running (without it, the recorder is skipped and the F8 hotkey does nothing).- The
FIVEMANAGE_MEDIA_API_KEYconvar is set in yourserver.cfg(the text file that holds your server's settings). This is the same keyoxide-policeuses for mugshots and bodycam — set it once and all three work.
The short version:
set FIVEMANAGE_MEDIA_API_KEY "your_key_here"For the full walkthrough (creating a free FiveManage account, making a Media token, and where to paste it), see the oxide-police guide: Setting up FiveManage media uploads.
If you set Config.Recording.provider = 'discord' instead, clips post to oxide-police's bodycam Discord webhook and no FiveManage key is needed.
Review configuration
Adjust shared/config.lua to fit your server. The most common keys to set first:
Config.AllowedJobs— map ofjobName -> { minGrade = N }. Defaults topoliceandLSPD. Officers must hold one of these jobs (at or above the listed grade) to install chips, see deployments, open the dashcam, and operate the remote.Config.UseRemoteController— whentrue(default), the only entry point is thebaitcar_remoteitem; the/baitcarcommand and the radial menu are disabled. Whenfalse,/baitcaropens the dashboard and the radial submenu is registered with your radial provider.Config.Recording.provider—'fivemanage'(default) or'discord'. Dashcam clips upload throughoxide-police. With'fivemanage', the credential is theFIVEMANAGE_MEDIA_API_KEYconvar in yourserver.cfg(the same keyoxide-policeuses for mugshots and bodycam). With'discord', clips post tooxide-police's bodycam Discord webhook. See Set up media uploads above.Config.Dispatch— alert code, title, icon, priority, and blip. Consumed byolink.dispatch.CreateAlertso the exact behavior depends on your dispatch adapter.
Full per-key reference: Configuration.
Tune dashcams for your fleet (optional)
The packaged camera profiles in Config.Dashcam.ByClass cover every stock GTA vehicle class. If you use a bait fleet of specific models (especially add-ons), tune them in-game with /baitcardashcamtune and paste the resulting snippet into Config.Dashcam.ByModel. See Admin for the full tuner workflow.
Verification
ox_lib, oxmysql, o-link, and oxide-baitcar all load without dependency errors.Config.AllowedJobs. Give yourself a baitcar_chip and, if UseRemoteController = true, a baitcar_remote./baitcar with UseRemoteController = false). The deployment should appear in the list. Lock and unlock the doors and confirm the icons reflect the state.Switch to a non-allowed character, drive the bait car off above Config.TriggerSpeedMps for at least Config.TriggerDurationMs. Confirm:
- The dispatch alert fires (depends on your dispatch adapter).
- All allowed-job clients receive a
Bait car triggered: <plate>toast. - A
triggeredrow appears inbaitcar_events. - If
Config.FakeWantedLevel > 0, the suspect picks up that wanted level.
oxide-police (Fivemanage or Discord) and surface in the MDT Video Storage tab.Optional Setup
Admin permission for the dashcam tuner
/baitcardashcamtune is available to any admin — anyone in your server's admin group. To make someone an admin, add them to the admin group and grant that group the admin ace in your server.cfg (the text file that tells your server which resources to start and holds your permission settings):
add_principal identifier.license:<their-license> group.admin
add_ace group.admin admin allowReplace <their-license> with the player's license identifier. If you already run QBCore, QBX, or ESX admins, no extra setup is needed — your existing framework admins pass automatically. See Admin for the full tuner workflow and key bindings.
Translations
All player-facing strings are loaded from locales/<lang>.json via lib.locale() (set SetConvar('ox:locale', '<lang>')). The shipped pack is locales/en.json; copy it to your locale code (e.g. locales/de.json) and translate the values to add another language.