MDT
Police, EMS and fire in one MDT: live CAD, civilian and vehicle records, warrants and BOLOs, patient care reports, fire and hazmat logs, and a live unit map.
Requirements
Required
qbx_coreox_liboxmysql
Optional
ox_targetorqb-target— right-click players in-world to open the MDT or issue a citation.
QBox only. Unlike the other XS scripts, this one lists qbx_core as a hard dependency. Standard QBCore (qb-core) is not supported.
Current release is v1.4.0.
Installation
- Place the
XS-MDTfolder in your resources directory, for exampleresources/[standalone]/XS-MDT. - Add
ensure XS-MDTtoserver.cfg. - Run
sql/mdt.sqlagainst your database. It usesCREATE TABLE IF NOT EXISTSthroughout, so re-running it is safe. - Set
Config.AuthorizedJobsto your police job names. - Restart the resource.
ensure XS-MDT
Upgrading from an earlier version? Run only the commented ALTER TABLE lines at the top of sql/mdt.sql that match the version you are coming from — not the whole file's worth of migrations.
Configuration
All options live in config.lua.
Access
Config.AuthorizedJobs = {
['police'] = true,
['sheriff'] = true,
['swat'] = true,
['statepolice'] = true,
}
Config.MinGrade = 0 -- 0 = every grade
Config.OpenKey = 'F9' -- players can rebind in GTA key bindings
Config.OnDutyOnly = true
Job names are case sensitive and must match your QBCore job names exactly.
Supervisor threshold
Config.SupervisorGrade = 3
Grade at or above this unlocks the supervisor-only features listed below.
Fines
Config.FineDeduction = {
Enabled = true,
AutoDeductArrests = true, -- deduct as soon as the arrest is logged
AutoDeductCitations = false, -- false = pay-later model (recommended)
AutoDeductOnMarkPaid = true, -- deduct when a supervisor marks it paid
}
Jail, audit and target
-- 'qb-prison' | 'ps-prison' | false
Config.JailResource = false
-- Leave empty to disable Discord audit posting
Config.AuditWebhook = ''
Config.Target = { Enabled = true, MaxDistance = 3.0 }
Config.BodyCam = { Enabled = true, RetentionDays = 30 } -- 0 = keep forever
Licences
Qbox tracks whether someone holds a licence. It has no idea whether that licence is currently any good, which is what an officer wants to know at a traffic stop. This adds the missing half.
Config.Licences = {
Enabled = true,
-- Suspending or revoking also flips the licence off in Qbox, so anything
-- else that checks it agrees with the MDT. It only ever touches licences
-- the character actually holds.
WriteBackToMetadata = true,
-- Revoking is supervisor-only. Suspending is not, because it happens
-- roadside and can be undone.
RevokeRequiresSupervisor = true,
Types = {
{ key = 'driver', label = 'Driver', icon = '🚗' },
{ key = 'weapon', label = 'Weapon', icon = '🔫' },
{ key = 'business', label = 'Business', icon = '💼' },
},
}
Supervisor features
Officers at or above Config.SupervisorGrade unlock:
- Department statistics — dashboard: weekly arrests, citations, fines and a top-officer leaderboard
- Department bulletins — post notices with Normal, Urgent or Critical priority, pinning and auto-expiry
- Audit log viewer — dashboard, with action, officer and date filters
- Body camera log viewer — Officers panel, per officer
- Edit penal codes — Penal Codes panel
- Mark citations paid — Citations panel
- Delete incidents — Incidents panel
Dispatch integration
Gunshots, crashes and fights are auto-detected. To send a call from another resource, use one of these.
Server export (recommended)
exports['XS-MDT']:CreateDispatchCall({
callType = 'BANK_ROBBERY',
description = 'Silent alarm triggered at Maze Bank.',
coords = { x = 148.0, y = -1044.0, z = 29.0 },
street = 'Alta Street', -- optional, auto-detected if omitted
callerName = '911 Caller', -- optional
})
Client event
TriggerEvent('XS-MDT:client:dispatch:custom',
'ROBBERY',
'Armed robbery in progress.',
vector3(24.8, -1347.3, 29.5)
)
Server net event
TriggerServerEvent('XS-MDT:server:autoDispatch', {
callType = 'SHOTS_FIRED',
description = 'Gunshots reported in the area.',
street = 'Forum Drive',
coords = { x = 80.0, y = -1283.0, z = 29.0 },
})
Built-in call type keys
SHOTS_FIRED Shots Fired FIGHT Fight / Assault VEHICLE_CRASH Vehicle Crash ROBBERY Robbery in Progress BANK_ROBBERY Bank Robbery STORE_ROBBERY Store Robbery TRAFFIC_STOP Traffic Stop MEDICAL Medical Emergency FIRE Structure Fire SUSPICIOUS Suspicious Activity
Any other key is displayed as the raw string. Keys are upper snake case — SHOTS_FIRED, not Shots Fired.
Auto-dispatch is rate limited to one call per player per call type every 60 seconds. If a test call does not appear, wait a minute before trying again.
Search, evidence and stats
Report search lives at the top of the reports tab. It searches case numbers, titles, narratives and officer names. Drafts stay private — another officer's unfinished report never shows up.
Evidence is logged from a report's detail view. Three types: photo (a direct image link, same as mugshots), item, or note. Every entry records who logged it and when, and shows on the printed report. You can remove your own entries for 15 minutes; after that it takes a supervisor — that's deliberate, an evidence log anyone can quietly edit isn't worth keeping.
Department stats sit on the dashboard for every police officer: arrests, citations, reports, active warrants and fines, with a daily chart and a 7/30 day toggle. The top-officers list only appears for supervisors, and the server decides that, not the panel.
Upgrading from 1.x: run the v1.6 → v2.0 block at the top of sql/mdt.sql. One new table.
Jailing
- Point
Config.JailResourceat your jail resource —'qb-prison'or'ps-prison'. - Log the arrest through ox_target on a live player. The modal receives that player's server ID, and filling in a jail time jails them on submit.
Arrests filed from the civilian search panel carry no source ID, so jailing does not fire. That is deliberate — the suspect may not be online or present. With Config.JailResource = false the script fires XS-MDT:client:jailPlayer instead so you can handle it yourself.
Server exports
-- Does this player have MDT access?
exports['XS-MDT']:IsAuthorized(src) -- boolean
-- Officer info for a player
exports['XS-MDT']:GetOfficerInfo(src)
-- { citizenid, name, job, grade, gradeLabel, onduty }
-- Write to the audit log (also fires the Discord webhook if configured)
exports['XS-MDT']:AuditLog('Action Name', 'Officer Name', 'Details string')
-- Create a dispatch call
exports['XS-MDT']:CreateDispatchCall({ callType, description, coords })Troubleshooting
The MDT will not open
Config.AuthorizedJobsmust contain the job name exactly, including case.- With
Config.OnDutyOnly = truethe player has to be clocked on. - The player's grade must be at or above
Config.MinGrade.
Dispatch alerts are not appearing
- Check the call type key format —
SHOTS_FIRED, notshots_fired. - The 60 second per-player, per-type rate limit may be in effect.
- Confirm the resource is running with
status XS-MDT.
SQL errors on start
Run sql/mdt.sql fully before first start. If upgrading, run only the ALTER TABLE blocks for your previous version.
Fines are not deducted
Config.FineDeduction.Enabledmust be true.- The player has to be online for an immediate deduction.
- Citations default to pay-later. Set
AutoDeductCitations = trueto deduct on creation.
Jailing does nothing
Config.JailResource must match your jail resource name exactly, and the arrest must be filed through ox_target on an online player.
Updating
- Back up the database. This resource owns records, incidents, warrants and audit history.
- Keep a copy of your edited
config.lua. - Run only the ALTER TABLE blocks in
sql/mdt.sqlfor the version you are upgrading from. - Replace the resource folder, merge your config back, restart.
Support
Include your qbx_core version, the job and grade you tested with, the full console error and the steps that caused it.