Skip to main content
The Jobs component manages character employment, on-duty status, and job-based permissions. Characters can have multiple jobs simultaneously, stored as an array on the character.

Overview

Access via Jobs (server-side only).

Multiple Jobs

Characters can hold multiple jobs at once (Jobs array)

Grade System

Each job has workplaces and grades with levels

On-Duty Status

Clock in/out system per job

Permissions

Job-based access control via Permissions sub-component
Server-Side Only: All job operations must be performed on the server. Never attempt to modify job data from the client.
Important: Characters have a Jobs array (plural), NOT a single Job field. Each entry in the array is a job object with Id, WorkplaceId, GradeId, and GradeLevel.

Job Data Structure

Characters store jobs as an array:
-- Character's Jobs field (accessed via char:GetData('Jobs'))
{
    {
        Id = "police",               -- Job identifier
        WorkplaceId = "lspd",        -- Workplace within the job
        GradeId = "officer",         -- Grade/rank identifier
        GradeLevel = 1               -- Numeric grade level
    },
    {
        Id = "mechanic",
        WorkplaceId = "bennys",
        GradeId = "employee",
        GradeLevel = 0
    }
}

Job Management

Get

Get a job definition by its ID.
Jobs:Get(jobId)
jobId
string
required
Job identifier (e.g., ‘police’, ‘ems’, ‘mechanic’)
jobDefinition
table|nil
Job definition data or nil if job doesn’t exist
Examples:
-- Get job definition
local jobDef = Jobs:Get('police')

if jobDef then
    print('Job found:', jobDef.Name)
end

GiveJob

Assign a job to a character. Characters can have multiple jobs.
Jobs:GiveJob(stateId, jobId, workplaceId, gradeId)
stateId
number
required
Character SID
jobId
string
required
Job identifier (e.g., ‘police’, ‘ems’, ‘mechanic’)
workplaceId
string
Workplace identifier within the job
gradeId
string
Grade/rank identifier within the workplace
Examples:
-- Give a character the police job
local char = Fetch:Source(source):GetData('Character')
local stateId = char:GetData('SID')

Jobs:GiveJob(stateId, 'police', 'lspd', 'officer')

-- Give a second job (characters can have multiple)
Jobs:GiveJob(stateId, 'mechanic', 'bennys', 'employee')

-- Hire player callback
Callbacks:RegisterServerCallback('jobs:hire', function(source, data, cb)
    local player = Fetch:Source(source)
    if not player then return cb(false) end

    local char = player:GetData('Character')
    if not char then return cb(false) end

    local stateId = char:GetData('SID')
    Jobs:GiveJob(stateId, data.jobId, data.workplaceId, data.gradeId)

    Notification:Success(source, 'You have been hired!')
    cb(true)
end)

RemoveJob

Remove a job from a character.
Jobs:RemoveJob(stateId, jobId)
stateId
number
required
Character SID
jobId
string
required
Job identifier to remove
Examples:
-- Fire a character from their police job
local char = Fetch:Source(source):GetData('Character')
local stateId = char:GetData('SID')

Jobs:RemoveJob(stateId, 'police')

-- Fire player callback
Callbacks:RegisterServerCallback('jobs:fire', function(source, data, cb)
    Jobs:RemoveJob(data.targetSID, data.jobId)

    -- Notify target if online
    local targetPlayer = Fetch:SID(data.targetSID)
    if targetPlayer then
        Notification:Error(targetPlayer:GetData('Source'), 'You have been fired!')
    end

    cb(true)
end)

Permissions

The Jobs.Permissions sub-component handles job-based access checks.

HasJob

Check if a player has a specific job.
Jobs.Permissions:HasJob(source, jobId, ...)
source
number
required
Player server source
jobId
string
required
Job identifier to check. Can pass multiple job IDs as additional arguments.
hasJob
boolean
true if the player has the specified job
Examples:
-- Check if player is police
if Jobs.Permissions:HasJob(source, 'police') then
    print('Player is police')
end

-- Check if player has any emergency job
if Jobs.Permissions:HasJob(source, 'police', 'ems') then
    print('Player is emergency services')
end

-- Gate access by job
Callbacks:RegisterServerCallback('police:openDoor', function(source, data, cb)
    if not Jobs.Permissions:HasJob(source, 'police') then
        return cb(false, 'Not authorized')
    end

    -- Open the door
    Doors:SetLock(data.doorId, false)
    cb(true)
end)

GetJobs

Get all jobs for a player.
Jobs.Permissions:GetJobs(source)
source
number
required
Player server source
jobs
table
Array of job objects the player has
Examples:
-- Get all of a player's jobs
local playerJobs = Jobs.Permissions:GetJobs(source)

for _, job in ipairs(playerJobs) do
    print('Job:', job.Id, 'Grade:', job.GradeId)
end

Duty System

The Jobs.Duty sub-component manages on-duty/off-duty status.

On

Clock a player in (set on-duty) for a specific job.
Jobs.Duty:On(source, jobId)
source
number
required
Player server source
jobId
string
required
Job identifier to clock in for
Examples:
-- Clock in for police duty
Jobs.Duty:On(source, 'police')

-- Clock in with validation
Callbacks:RegisterServerCallback('jobs:clockIn', function(source, data, cb)
    if not Jobs.Permissions:HasJob(source, data.jobId) then
        return cb(false, 'You do not have this job')
    end

    Jobs.Duty:On(source, data.jobId)

    Notification:Success(source, 'You are now on duty')
    cb(true)
end)

Off

Clock a player out (set off-duty) for a specific job.
Jobs.Duty:Off(source, jobId)
source
number
required
Player server source
jobId
string
required
Job identifier to clock out of
Examples:
-- Clock out of police duty
Jobs.Duty:Off(source, 'police')

-- Clock out with cleanup
Callbacks:RegisterServerCallback('jobs:clockOut', function(source, data, cb)
    Jobs.Duty:Off(source, data.jobId)

    -- Remove job equipment
    RemoveJobEquipment(source, data.jobId)

    Notification:Info(source, 'You are now off duty')
    cb(true)
end)

GetDutyData

Get duty information for a specific job, including all on-duty players.
Jobs.Duty:GetDutyData(jobId)
jobId
string
required
Job identifier
dutyData
table
Duty data including DutyPlayers array
{
    DutyPlayers = { source1, source2, ... }
}
Examples:
-- Get on-duty police officers
local dutyData = Jobs.Duty:GetDutyData('police')
local onDutyPolice = dutyData.DutyPlayers

print('On-duty police:', #onDutyPolice)

-- Notify all on-duty police
function NotifyPolice(message)
    local dutyData = Jobs.Duty:GetDutyData('police')

    for _, src in ipairs(dutyData.DutyPlayers) do
        Notification:Info(src, message)
    end
end

-- Check if any police are on duty
function AnyPoliceOnDuty()
    local dutyData = Jobs.Duty:GetDutyData('police')
    return #dutyData.DutyPlayers > 0
end

-- Dispatch system
function DispatchCall(callType, location)
    local dutyData = Jobs.Duty:GetDutyData('police')

    for _, src in ipairs(dutyData.DutyPlayers) do
        TriggerClientEvent('mythic-dispatch:client:NewCall', src, {
            type = callType,
            location = location,
            timestamp = os.time()
        })
    end
end

Accessing Character Jobs Directly

You can also access a character’s jobs through the DataStore:
-- Get character's jobs array
local player = Fetch:Source(source)
local char = player:GetData('Character')
local jobs = char:GetData('Jobs')  -- Returns array of job objects

-- Check if character has a specific job
for _, job in ipairs(jobs) do
    if job.Id == 'police' then
        print('Character is police')
        print('Workplace:', job.WorkplaceId)
        print('Grade:', job.GradeId, 'Level:', job.GradeLevel)
    end
end
While you can read char:GetData('Jobs') directly, always use Jobs:GiveJob() and Jobs:RemoveJob() to modify jobs, and Jobs.Permissions:HasJob() for checking job access. Do NOT use char:SetData('Jobs', ...) directly.

Best Practices

Always use Jobs.Permissions:HasJob() for access checks:
-- ✅ Good: Use permissions component
if Jobs.Permissions:HasJob(source, 'police') then
    -- Allow access
end

-- ❌ Bad: Manually checking Jobs array
local char = Fetch:Source(source):GetData('Character')
local jobs = char:GetData('Jobs')
for _, j in ipairs(jobs) do
    if j.Id == 'police' then ... end
end
function CleanClockOut(source, jobId)
    -- Clock out
    Jobs.Duty:Off(source, jobId)

    -- Remove job items
    RemoveJobItems(source)

    -- End active tasks
    EndActiveTasks(source)

    -- Remove job vehicles
    RemoveJobVehicles(source)
end
-- Check if player has any emergency service job
if Jobs.Permissions:HasJob(source, 'police', 'ems', 'fire') then
    -- Player is emergency services
end

Next Steps

Jobs - Events

Job-related events

Jobs - Configuration

Job definitions and setup

Characters API

Character management

Job System Guide

Complete job system guide
Job Integration: Use Jobs.Permissions:HasJob(source, jobId) for access checks and Jobs.Duty:GetDutyData(jobId).DutyPlayers to find on-duty players. These are the two most commonly used patterns.