Skip to main content
sh-policejob is a full-featured law enforcement resource for RedM roleplay servers. It lets you run police, sheriff, and marshal departments simultaneously under a single script, each with their own grade tables, duty stations, and payroll rules. Whether you’re running a small community or a large public server, sh-policejob gives you the tools to build a realistic, configurable law enforcement experience out of the box.

Overview

DetailValue
FrameworksVORP, RSG
LocalizationEnglish, Spanish, German
Interaction Modesprompt, text3d, target (ox_target)

Features

Run police, sheriff, and marshal departments simultaneously. Each job has its own independent grade table, duty stations, payroll configuration, and management panel access. Officers only see options relevant to their assigned department.
Define clock-in points as world markers or NPC characters. Each station generates a map blip so players can find their nearest station without guesswork. Officers must clock in at a station before accessing job-locked features.
Officers receive automatic salary payments on a configurable timer (default: 15-minute intervals). Pay rates are set per grade, per job, so a senior marshal earns more than a rookie deputy without any manual handling.
Eight pre-configured patrol zones cover the map, each with a configurable pay multiplier. Assign officers to zones and reward active patrol work with bonus earnings on top of their base salary.
The cuff flow covers the full interaction chain: cuff, uncuff, and lockpick. Item consumption is fully configurable — cuffs and keys do not consume by default, while the lockpick item is consumed on use. Players can attempt a lockpick escape using the lockpick item. Cuffed state persists across server restarts via cuffed.json.
Officers can search players and seize cash up to a configurable percentage. Evidence items can be flagged during a search and logged automatically to your Discord webhook. All search and seizure events are timestamped and attributed to the searching officer.
Officers equip a detective kit item and inspect bodies or NPCs to generate cause-of-death information. Results appear in the HUD and can be saved to a report. Useful for investigation roleplay without requiring external scripts.
Equippable 3D prop badges for marshal, sheriff, and detective roles. Officers can adjust badge position in-game using an interactive placement tool, so the badge sits correctly on any clothing combination.
Spawn and manage prisoner transport wagons from the job menu. Load suspects directly into the wagon — each wagon holds up to 4 prisoners by default (configurable up to 6). The wagon system tracks loaded suspects and logs transport events to Discord.
A NUI officer management panel lets senior officers hire, fire, and change grades for department members. Access is grade-locked — you configure the minimum grade required to open the panel per location.
Shared armories and configurable supply stores give on-duty officers access to job-specific equipment. Stock lists, prices, and grade requirements are all set in config.
Gunshot detection triggers an alert to all on-duty officers after a configurable delay. The alert includes a map blip and the nearest town name so officers can respond without needing exact coordinates. Radius, cooldown, and minimum online officers are all configurable.
Separate webhook URLs for each system: duty events, patrol zone changes, searches, seizures, wagon transports, and witness alerts. Route different events to different channels for clean log organization.

Installation

1

Add the resource

Place the sh-policejob folder inside your server’s resources directory.
2

Configure your framework

Open config.lua and set Config.Framework to either 'vorp' or 'rsg' to match your server.
3

Set up your departments

Define your active jobs in Config.PoliceJobs. You can enable all three departments or just the ones your server uses.
4

Add required items

Ensure handcuffs, handcuffkey, and lockpick exist as valid items in your framework’s item registry. Add them to any shops or starter kits as needed.
5

Configure webhooks

Add your Discord webhook URLs to the Config.Webhooks section of config.lua. Leave any webhook blank to disable logging for that system.
6

Start the resource

Add ensure sh-policejob to your server.cfg and restart your server.

Configuration

The main configuration file is config.lua in the resource root. The most commonly edited options are shown below.
config.lua
Config.Framework = 'vorp'  -- 'vorp' or 'rsg'

Config.PoliceJobs = {
    { name = 'police',  label = 'Lawman',  minGrade = 0 },
    { name = 'sheriff', label = 'Sheriff', minGrade = 0 },
    { name = 'marshal', label = 'Marshal', minGrade = 0 },
}

Config.Payroll = {
    Enabled         = true,
    IntervalMinutes = 15,        -- Minutes between payments
    CurrencyVORP    = 0,         -- 0 = cash, 1 = gold
    CurrencyRSG     = 'cash',
    Jobs = {
        police = {
            [0] = 15, [1] = 35, [2] = 55, [3] = 65,
            [4] = 100, [5] = 125, [6] = 190, [7] = 225, [8] = 250,
        },
    },
}

Config.Cuffs = {
    CuffItem         = 'handcuffs',
    KeyItem          = 'handcuffkey',
    LockpickItem     = 'lockpick',
    ConsumeCuffs     = false,    -- handcuffs not consumed on use by default
    ConsumeKeys      = false,
    ConsumeLockpick  = true,     -- lockpick consumed on use
    RequireOnDuty    = true,
}

Config.Witness = {
    Enabled          = true,
    Radius           = 30.0,
    TimeToNotify     = 15,       -- Seconds after shooting before notifying police
    CooldownSeconds  = 30,       -- Same-shooter cooldown in seconds
    MinPoliceOnline  = 1,
}

Config.Webhooks = {
    Duty      = '',
    Patrol    = '',
    Search    = '',
    Seizure   = '',
    Wagon     = '',
    Alerts    = '',
}

Interaction Modes

Uses RedM’s native interaction prompt system. No additional dependencies required. This is the default mode.
config.lua
Config.InteractionMode = 'prompt'

Server Exports

Use these exports to read officer state from other resources on your server.
-- Check if a player is currently on duty
local isOnDuty = exports['sh-policejob']:IsPlayerOnDuty(source)

-- Get the number of on-duty officers at or above a minimum grade
local count = exports['sh-policejob']:GetOnDutyCount(minGrade, jobName)

-- Get a table of all on-duty officers matching the filter
local officers = exports['sh-policejob']:GetOnDutyOfficers(minGrade, jobName)

-- Get full duty data for a specific player
local dutyData = exports['sh-policejob']:GetPlayerDutyData(source)
-- Returns: { job, grade, stationId, startedAt }
All exports return nil if the player does not exist or is not on duty. Always nil-check the return value before using it in downstream logic.

Compatibility

sh-policejob stores cuffed state in cuffed.json in the resource folder. If you delete the resource and reinstall it, cuffed state is lost. Back up this file before any reinstall if players may be cuffed at the time.
If you are also running sh-policemdt, set the same Config.Framework value in both resources. The MDT reads on-duty state directly from sh-policejob’s exports, so the framework must match for character data to resolve correctly.