Exports
For developers: which export path to use, worked scenarios and the events.
- Exports: Reads — The read exports: settings, identity and PNC boards, duty, roster and calls.
- Exports: System Writes — The write exports for systems such as a CAD or dispatch bridge.
- Exports: Officer Writes — The write exports that act as an on-duty officer.
- Exports: Client — The client-side exports.
Which path
Section titled “Which path”Pick a path before you pick a function name.
| You are | Use | First argument | Audit stamp |
|---|---|---|---|
| A tablet or CAD terminal a player is using | Officer / terminal (*ByServerId) |
That player’s source |
Their Rockstar license |
| Another resource with no player (AI dispatch, ERS, webhook, cron) | System writes (no ByServerId, no source) |
Payload table only | system:<your resource> |
| Only reading or listening | Reads and Events | None | None |
There is no extra dispatcher login and no hidden dispatcher-only API. Dispatch for a human is rank permissions on a clocked-in officer (dispatch.assign_units, dispatch.resolve_call, …). An automated dispatcher is the system path.
Shared contracts
Section titled “Shared contracts”- Call pattern:
exports['night_shifts_mdt']:ExportName(...)— use the colon. Dot or bracket calls can drop the first argument on CitizenFX. - Writes:
callback(ok, resultOrError)—ok == trueandresultis a table, orok == falseand the second arg is an error string. Exception:ForwardCallToMDTreturns the new call id as a number in the second arg. - Board / catalogue reads: pass a callback. The list arrives in that function (MySQL). Do not document or rely on the no-callback memory snapshot.
- System audit:
performedByissystem:<your resource folder>. User FK columns (issuedBy,createdBy,executedBy, …) stayNULL. - Officer: first arg is a connected, clocked-in
source. Audit is that player’s license. Creates inferdepartmentIdfrom the active shift. - Trust model: any started server resource can call these. There is no allowlist.
- Identity: civilians are
nsmdt_civiliansrows (id/personalId). Vehicles belong to a civilian (civId). Warrants, flags, and fines targetcivId. Do not invent rows with SQL.
How to read a card
Section titled “How to read a card”Every export below is a server Lua call unless the heading says Client. Each card has a copy-paste Example under the tables — that is the call you write.
| Word on the card | What you write | What you get |
|---|---|---|
| sync | local x = exports['night_shifts_mdt']:GetVersion() |
The value is the return of the export. No callback. |
| callback (reads) | exports['night_shifts_mdt']:GetPenalCodes(function(rows) … end) |
The list arrives inside that function. The export itself returns nothing useful. |
| callback ok / fail (writes) | exports['night_shifts_mdt']:SetCallStatus({ … }, function(ok, result) … end) |
ok == true → result is a table. ok == false → result is an error string. |
Pairing index (same job, two exports)
Section titled “Pairing index (same job, two exports)”| Job | System (no player) | Officer / terminal |
|---|---|---|
| Assign unit to call | AssignUnitToCall |
AssignUnitToCallByServerId |
| Detach unit | DetachUnitFromCall |
DetachUnitFromCallByServerId |
| Resolve or reopen call | SetCallStatus |
SetCallStatusByServerId |
| Archive resolved call | ArchiveCall |
ArchiveCallByServerId |
| Edit call details | EditCall |
EditCallByServerId |
| Add or delete call note | AddCallNote / DeleteCallNote |
AddCallNoteByServerId / DeleteCallNoteByServerId |
| Unit on-call status | SetUnitOnCallStatus |
SetUnitOnCallStatusByServerId |
| Create / cancel / execute / update warrant | CreateWarrant / CancelWarrant / ExecuteWarrant / UpdateWarrant |
same names + ByServerId |
| Create or toggle flag | CreateFlag / SetFlagActive |
same names + ByServerId |
| Create or mark fine paid | CreateFine / MarkFinePaid |
same names + ByServerId |
| Record and charges | CreateCriminalRecord / AddCharge / UpdateCharge |
same names + ByServerId |
| Vehicle BOLO | SetVehicleBolo |
SetVehicleBoloByServerId |
| ANPR registry | AddANPRRegistry / RemoveANPRRegistry |
same names + ByServerId |
| Medical / licenses / notes / bulletins / council | unsuffixed names | same names + ByServerId |
Create-call has no officer twin: ForwardCallToMDT is already a system write.
Worked scenarios
Section titled “Worked scenarios”AI / system dispatch
local mdt = exports['night_shifts_mdt']
AddEventHandler('nightshifts:mdt:server:callCreated', function(callId, call) print(('AI saw call %s (%s)'):format(callId, call.callType))end)
mdt:ForwardCallToMDT({ callType = 'Welfare Check', description = 'Neighbour reports no answer at the door.', x = 215.4, y = -810.2, z = 30.7, postal = '7284', street = 'San Andreas Ave', zone = 'Downtown', callerName = 'AI Dispatch', contactDetails = 'system', requiresPolice = 1, requiresAmbulance = 0, requiresFire = 0, requiresTow = 0, requiresCouncil = 0, priority = 3,}, function(ok, callId) if not ok then return print(callId) end mdt:AssignUnitToCall({ callId = callId, callsign = 'L-42' }, function(ok2, err) if not ok2 then return print(err) end mdt:AddCallNote({ callId = callId, note = 'Assigned by AI CAD (my_ai_dispatch)' }, function() mdt:SetCallStatus({ callId = callId, status = 'resolved' }, function(ok3) if ok3 then mdt:ArchiveCall({ callId = callId, reason = 'Complete' }) end end) end) end)end)Officer terminal (warrant)
local mdt = exports['night_shifts_mdt']if not mdt:HasPermissionByServerId(source, 'pnc.registration') then return endmdt:CreateWarrantByServerId(source, { civId = 42, warrantType = 'arrest', reason = 'Assault — issued from CAD terminal', expiresAt = '2027-06-01 00:00:00',}, function(ok, result) if not ok then return print(result) end print('warrant', result.warrantNumber)end)Read + event catch-up
AddEventHandler('onResourceStart', function(res) if res ~= GetCurrentResourceName() then return end local calls = exports['night_shifts_mdt']:GetActiveCalls() print('catch-up active calls', #calls)end)AddEventHandler('nightshifts:mdt:server:callUpdated', function(callId, call) print('updated', callId, call.callStatus)end)Events
Section titled “Events”When to use: React live when a call or PNC row is created or updated. Subscribe with server AddEventHandler (not RegisterNetEvent).
When not to: Do not TriggerEvent these yourself to “refresh” the MDT.
| Event | Fired when | Args |
|---|---|---|
nightshifts:mdt:server:callCreated |
New call (including ForwardCallToMDT) |
callId, call |
nightshifts:mdt:server:callUpdated |
Assign, detach, status, edit, archive, notes | callId, call |
nightshifts:mdt:server:warrantCreated |
Warrant insert | warrantId, payload |
nightshifts:mdt:server:warrantUpdated |
Cancel / execute / edit | warrantId, payload |
nightshifts:mdt:server:flagCreated |
Flag insert | flagId, payload |
nightshifts:mdt:server:flagUpdated |
Flag active toggle | flagId, payload |
nightshifts:mdt:server:fineCreated |
Fine insert | fineId, payload |
nightshifts:mdt:server:fineUpdated |
Mark paid | fineId, payload |
nightshifts:mdt:server:recordCreated |
Criminal record insert | recordId, payload |
nightshifts:mdt:server:recordUpdated |
Charge added | recordId, payload |
AddEventHandler('nightshifts:mdt:server:callCreated', function(callId, call) print('new call', callId, call.callType)end)AddEventHandler('nightshifts:mdt:server:warrantCreated', function(warrantId, payload) print('warrant', warrantId, payload.warrantNumber)end)Use GetActiveCalls / GetCallById for catch-up after your resource starts.