Skip to content

DutyKit

DutyKit turns approved duty vehicles into configurable gear access points for your server-defined items, weapons, outfits, kits, and refill flows. Use it for police, EMS, fire, DOT, mechanics, roadside support, or custom jobs that should collect duty equipment from vehicles instead of carrying everything at all times.

  • ox_lib v3.30.0 or newer.

  • One supported framework running on the server:

    • ox_core
    • qbx_core
    • qb-core
    • es_extended
  • One supported inventory adapter set in Config.inventory:

    • ox_inventory
    • codem-inventory
    • qb-inventory
    • origen_inventory
    • tgiann-inventory
  • One supported target adapter set in Config.target:

    • ox_target
    • qb-target
    • sleepless_interact
  1. Download the latest granted asset from the Cfx.re Portal.

  2. Install and start your selected framework, inventory, target resource, and ox_lib.

  3. Place the kf-dutykit folder in your server resources directory.

  4. Start dependencies before DutyKit.

    server.cfg
    ensure ox_lib
    # Start your framework.
    ensure qbx_core
    # ensure qb-core
    # ensure es_extended
    # ensure ox_core
    # Start your inventory.
    ensure ox_inventory
    # Start your target resource.
    ensure ox_target
    ensure kf-dutykit
  5. Open config.lua and set the inventory, target, and notification adapters for your server.

    config.lua
    Config.inventory = 'ox_inventory'
    Config.target = 'ox_target'
    Config.notifications = true
    Config.notify = 'ox_lib'
  6. Add or edit loadout files in loadouts/, then register those files in loadouts/index.lua.

DutyKit adds vehicle interactions for loadouts and refills through your configured target resource. Players open the interaction from an approved vehicle, then use the menu to browse Quick Access, all loadouts, or the custom categories defined in Config.categories.

A loadout can contain either inventory items or clothing. DutyKit checks vehicle access, ped model access, group access, distance, and item limits server-side before giving equipment or applying an outfit.

Quick Access is controlled per item or clothing entry with quick = true. allowEquipAll = true enables the bulk equip option for a loadout.

config.lua
-- Documentation: https://konflict-docs.pages.dev/resources/dutykit/
Config = {}
-- The inventory resource to use
-- one of: 'auto', 'ox_inventory', 'codem-inventory', 'qb-inventory', 'qs-inventory', 'origen_inventory', 'tgiann-inventory'
Config.inventory = 'auto'
-- The target resource to use, one of: 'ox_target', 'sleepless_interact', or 'qb-target'
Config.target = 'ox_target'
Config.notifications = true
-- The notification resource to use, one of: 'ox_lib', 'esx_notify', 'qb-core', 'qbx_core', 'okokNotify', 'wasabi_notify'
Config.notify = 'ox_lib'
-- Loadout menu categories
Config.categories = { 'Items', 'Outfits' }
Config.debug = false
-- ACE principal who can use /dutykit and save loadout editor changes.
Config.dutykitCommandACE = 'group.admin'
Config.loadoutMenuThemeDefaultPreset = 'police'
Config.loadoutMenuThemePresets = {
default = {
selectedColor = 'rgba(0, 0, 0, 0.7)',
selectedBorderColor = 'rgba(210, 210, 210, 0.7)',
selectedColorLine = 'rgba(30, 30, 30, 1.0)',
buttonGradientFrom = 'rgba(56, 56, 56, 0.7)',
buttonGradientTo = 'rgba(74, 74, 74, 0.5)',
textColor = 'rgba(230, 230, 230, 1.0)',
disabledTextColor = 'rgba(163, 163, 163, 1.0)',
indicatorColor = 'rgba(230, 230, 230, 1.0)',
},
police = {
selectedColor = 'rgba(5, 21, 41, 0.7)',
selectedBorderColor = 'rgba(140, 190, 255, 0.7)',
selectedColorLine = 'rgba(84, 156, 255, 1.0)',
buttonGradientFrom = 'rgba(14, 34, 62, 0.7)',
buttonGradientTo = 'rgba(27, 55, 92, 0.5)',
textColor = 'rgba(227, 239, 255, 1.0)',
disabledTextColor = 'rgba(146, 166, 194, 1.0)',
indicatorColor = 'rgba(129, 192, 255, 1.0)',
},
ems = {
selectedColor = 'rgba(0, 77, 72, 0.7)',
selectedBorderColor = 'rgba(160, 232, 222, 0.7)',
selectedColorLine = 'rgba(36, 204, 181, 1.0)',
buttonGradientFrom = 'rgba(0, 92, 86, 0.7)',
buttonGradientTo = 'rgba(0, 132, 119, 0.5)',
textColor = 'rgba(232, 255, 251, 1.0)',
disabledTextColor = 'rgba(145, 190, 184, 1.0)',
indicatorColor = 'rgba(255, 221, 76, 1.0)',
},
fire = {
selectedColor = 'rgba(59, 17, 4, 0.7)',
selectedBorderColor = 'rgba(255, 206, 122, 0.7)',
selectedColorLine = 'rgba(255, 118, 34, 1.0)',
buttonGradientFrom = 'rgba(90, 30, 8, 0.7)',
buttonGradientTo = 'rgba(128, 51, 16, 0.5)',
textColor = 'rgba(255, 241, 226, 1.0)',
disabledTextColor = 'rgba(201, 168, 141, 1.0)',
indicatorColor = 'rgba(255, 193, 94, 1.0)',
},
}
Config.autoEquipWeapon = true -- Auto equip weapon items when they are retrieved
Config.checkVehicleLocked = true -- Check if vehicle is locked before allowing access
Config.refillTime = '1 hour' -- Time between refills (e.g. 10 minutes, 1 hour, 2 days)
Config.liveLoadoutReload = true -- Reload runtime loadouts and sync clients after admin saves without resource restart
Config.loadoutReloadBps = 128000 -- Latent event bytes per second for live loadout reloads after admin saves
-- Animation when opening loadout menu
Config.openLoadoutAnim = {
animDictionary = 'anim@heists@prison_heiststation@cop_reactions',
animationName = 'cop_b_idle',
blendInSpeed = 3.0,
blendOutSpeed = 3.0,
duration = -1,
animFlags = 16,
startPhase = 0.0,
phaseControlled = false,
controlFlags = 0,
}
Config.refillStations = {
{
label = 'Police Armory',
type = {'police'},
groups = {'police', 'sheriff'},
coords = vector3(452.8355, -1019.4484, 27.0),
distance = 3.0,
marker = {
enabled = true,
scale = vector3(3.0, 3.0, 3.0),
type = 1,
color = {r = 255, g = 0, b = 0, a = 70},
},
drawText = true,
blip = {
enabled = true,
sprite = 110,
color = 3,
scale = 0.8,
},
},
{
label = 'Sheriff Armory',
groups = {'sheriff'},
coords = vector3(-1896.8083, -3053.5139, 13.9443),
blip = {
enabled = true,
sprite = 110,
color = 5,
scale = 0.8,
label = 'Custom Armory Label',
}
}
}

DutyKit includes configurable menu theme presets in Config.loadoutMenuThemePresets. The default config includes presets such as default, police, ems, and fire.

Set a loadout theme by name:

theme = 'police'

Or add your own preset in Config.loadoutMenuThemePresets:

config.lua
Config.loadoutMenuThemePresets.custom = {
selectedColor = 'rgba(10, 10, 10, 0.7)',
selectedBorderColor = 'rgba(255, 255, 255, 0.7)',
selectedColorLine = 'rgba(255, 255, 255, 1.0)',
buttonGradientFrom = 'rgba(30, 30, 30, 0.7)',
buttonGradientTo = 'rgba(50, 50, 50, 0.5)',
textColor = 'rgba(240, 240, 240, 1.0)',
disabledTextColor = 'rgba(150, 150, 150, 1.0)',
indicatorColor = 'rgba(240, 240, 240, 1.0)',
}

Use groups to restrict a loadout or refill station to jobs, gangs, or framework groups.

-- Any listed group can access.
groups = { 'police', 'sheriff' }
-- Minimum grade requirements.
groups = { police = 2, sheriff = 1 }
-- Mixed plain and graded access.
groups = { police = 2, sheriff = true }
-- ESX also supports grade name checks.
groups = { police = 'boss' }

Framework behavior:

FrameworkGroup source
ox_coreOx player groups.
qbx_corePlayer job, gang, or native Qbox groups.
qb-corePlayer job and gang.
es_extendedPlayer job, including numeric grade or grade name.

DutyKit includes an in-game loadout editor for creating and maintaining loadouts. This is the expected workflow for most server owners because it avoids manually writing each loadout table from scratch.

Use the /dutykit command in-game to open the editor. From there, you can build loadouts, assign categories, items, outfits, vehicles, groups, limits, Quick Access entries, and themes, then save the result. DutyKit stores those saved loadouts as Lua files inside loadouts/.

By default, /dutykit is restricted to group.admin through Config.dutykitCommandACE.

DutyKit stores loadouts as individual Lua files in loadouts/. The in-game editor can create and update these files for you, but understanding the structure is useful for developers, Git review, and advanced manual edits.

DutyKit loads loadouts from loadouts/index.lua. Each index entry points to a Lua file in loadouts/, and each loadout file returns one table.

When manually adding, renaming, or removing loadout files, run /dutykit rebuild in-game to rebuild loadouts/index.lua so DutyKit knows which loadout files should be loaded.

loadouts/index.lua
return {
{
file = 'ld_police_patrol-kit.lua',
id = 'ld_police_patrol',
},
}
loadouts/ld_police_patrol-kit.lua
return {
category = 'Items',
label = 'Patrol Weapon Kit',
type = 'police',
theme = 'police',
groups = {
'police',
'sheriff',
},
allowedVehicles = {
'police',
'police2',
'police3',
'sheriff',
'sheriff2',
},
allowEquipAll = true,
items = {
{
itemName = 'WEAPON_COMBATPISTOL',
ammo = 72,
componentItemNames = {
'at_flashlight',
},
tint = 0,
max = 1,
quick = true,
},
{
itemName = 'WEAPON_STUNGUN',
max = 1,
quick = true,
},
{
itemName = 'ammo-9',
max = 8,
},
{
itemName = 'security_card',
metadata = {
label = 'Patrol Access Card',
description = 'Access card issued for patrol duty.',
clearance = 2,
active = true,
},
strictMetadataMatch = true,
max = 1,
},
},
}
loadouts/ld_police_patrol-uniform.lua
return {
category = 'Outfits',
label = 'Patrol Uniform',
type = 'police',
theme = 'police',
groups = {
'police',
'sheriff',
},
allowedVehicles = {
'police',
'police2',
'police3',
'sheriff',
'sheriff2',
},
allowedPeds = {
'mp_m_freemode_01',
'mp_f_freemode_01',
},
allowEquipAll = true,
clothing = {
{
label = 'Short Sleeve Arms',
componentId = 3,
drawable = 19,
texture = 0,
quick = true,
},
{
label = 'Duty Pants',
componentId = 4,
drawable = 35,
texture = 0,
quick = true,
},
{
label = 'Duty Cap',
propId = 0,
drawable = 46,
texture = 0,
quick = true,
},
},
}
FieldDescription
categoryMenu category. Should match a value in Config.categories.
labelDisplay name shown in the DutyKit menu.
typeOptional family key used to pair loadouts with refill stations.
themeTheme preset name or inline theme table.
groupsOptional access filter. Omit it for unrestricted access.
allowedVehiclesVehicle spawn names allowed to open this loadout. Omit or leave empty to allow any vehicle.
allowedPedsOptional ped model allowlist for outfit or role-specific loadouts.
allowEquipAllEnables Equip All or Equip Quick menu actions.
itemsInventory items, weapons, ammo, supplies, or tools.
clothingOutfit components and props.
resetMissingClothingFor outfit loadouts, set false to avoid clearing missing clothing slots during Equip All.
FieldDescription
itemNameInventory item name. Use the exact item key from your inventory.
maxMaximum number of this item that can be retrieved from the same vehicle state before it is returned or refilled.
ammoWeapon ammo metadata.
componentItemNamesWeapon component item names.
tintWeapon tint metadata.
metadataFlat custom item metadata table. Supports string, number, and boolean values.
strictMetadataMatchRequires returned items to match the custom metadata fields configured on this loadout item.
quickShows the item in Quick Access.
autoEquipWeaponSet false to prevent auto-equip for this item when Config.autoEquipWeapon is enabled.
FieldDescription
labelDisplay label in the menu.
componentIdGTA clothing component ID.
propIdGTA prop ID. Use propId = -1 to clear all props for that entry.
drawableDrawable variation.
textureTexture variation.
quickShows the clothing entry in Quick Access.

Configure static refill points in Config.refillStations. Use matching type values when a station should only refill a specific loadout family.

config.lua
Config.refillStations = {
{
label = 'Police Armory',
type = { 'police' },
groups = { 'police', 'sheriff' },
coords = vector3(-1833.0654, -3150.6802, 13.0),
distance = 3.0,
marker = {
enabled = true,
scale = vector3(3.0, 3.0, 3.0),
type = 1,
color = { r = 255, g = 0, b = 0, a = 70 },
},
drawText = true,
blip = {
enabled = true,
sprite = 110,
color = 3,
scale = 0.8,
},
},
}
FieldDescription
labelMenu, marker, or blip label.
typeOptional station type. Pair it with loadout type values.
groupsOptional access filter for the station.
coordsWorld position for the refill station.
distanceInteraction distance. Defaults to 10 if omitted.
drawTextEnables station draw text when supported by the client flow.
markerOptional FiveM marker configuration. See Marker fields.
blipOptional map blip configuration. See Blip fields.

Use marker to draw a FiveM marker at the refill station.

marker = {
enabled = true,
scale = vector3(3.0, 3.0, 3.0),
type = 1,
direction = vector3(0.0, 0.0, 0.0),
rot = vector3(0.0, 0.0, 0.0),
color = { r = 255, g = 0, b = 0, a = 70 },
bobUpAndDown = false,
faceCamera = false,
rotate = false,
textureDict = nil,
textureName = nil,
}

Use blip to create a map blip for the refill station.

blip = {
enabled = true,
sprite = 110,
color = 3,
scale = 0.8,
label = 'Police Armory',
}
FieldDescription
enabledEnables or disables the map blip.
spriteBlip sprite ID. Defaults to 110 if omitted.
colorBlip color ID. Defaults to 3 if omitted.
scaleBlip size. Defaults to 0.8 if omitted.
labelOptional custom blip label. If omitted, DutyKit uses the refill station label.
  • DutyKit adds target options to vehicle boot/trunk access for loadouts and refills.
  • Vehicle access checks are performed again on the server before giving items or applying loadouts.
  • The server rejects access when the player is too far from the target vehicle.
  • If Config.checkVehicleLocked is enabled, locked vehicles cannot be opened for DutyKit access.
  • If the configured trunk door is unavailable, DutyKit falls back to rear doors where possible.

The following files are escrow-ignored so you can adapt DutyKit to custom server stacks without editing locked code:

  • config.lua
  • loadouts/*.lua
  • server/framework/*.lua
  • server/inventory/*.lua
  • client/framework/*.lua
  • client/inventory/*.lua
  • client/interface/*.lua
  • client/target/*.lua

Inventory adapters are expected to provide:

-- Client
ClientFramework.getItem(itemName)
ClientFramework.getItems()
ClientFramework.useSlot(slot)
-- Server
ServerFramework.getItem(itemName)
ServerFramework.getItems()
ServerFramework.addInventoryItem(player, itemName, amount, metadata)
ServerFramework.removeInventoryItem(player, itemName, amount, metadata, slot)
ServerFramework.getInventoryItemBySlot(player, slot)
ServerFramework.getSlotIdWithItem(player, itemName, metadata)

Framework adapters are expected to provide:

GetPlayer(source)
ServerFramework.IsPlayerInGroup(player, groups)
ClientFramework.IsPlayerInGroup(groups)

Check that:

  • Config.target matches the target resource you are running.
  • The target resource starts before kf-dutykit.
  • The vehicle spawn name is listed in allowedVehicles.
  • The player is in one of the configured groups.
  • The player’s ped model is allowed by allowedPeds, if set.
  • The vehicle is not locked when Config.checkVehicleLocked = true.

Check that:

  • Config.inventory matches your inventory resource.
  • The inventory resource starts before kf-dutykit.
  • Every itemName exactly matches an item registered in your inventory.
  • Weapon component item names exist in your inventory when using componentItemNames.

Enable debug mode, put on the desired outfit, run /dumpappearance, then copy the printed component and prop rows into your outfit loadout file.

Players can see a loadout but cannot retrieve items

Section titled “Players can see a loadout but cannot retrieve items”

Check server logs for access-denied or inventory adapter errors. The server validates the entity, distance, group filter, vehicle allowlist, ped allowlist, and inventory add result before completing the request.