# Custom slots

Other resources can add equipment slots. They appear in the equipment panel under their `group`, are saved per player like the built-in slots, count toward the carry weight, show up in searches and in the ox-style APIs, and the server checks every move into them.

```lua title="server.lua"
local function registerSlots()
    exports['koja-inventory-v2']:RegisterSlot({
        key = 'radio',
        label = 'Radio',
        accepts = { 'radio' },
        group = 'gear',
        icon = 'radio',
        size = { 2, 3 },
    })

    exports['koja-inventory-v2']:RegisterSlot({
        key = 'badge',
        label = 'Badge',
        accepts = { categories = { 'badge' }, items = { 'lspd_badge', 'bcso_badge' } },
        group = 'police',
        icon = 'badge',
    })

    exports['koja-inventory-v2']:RegisterSlot({
        key = 'holster',
        label = 'Holster',
        group = 'police',
        icon = 'shield',
        accepts = function(itemName, source, slotKey)
            return itemName == 'WEAPON_STUNGUN' or itemName == 'WEAPON_COMBATPISTOL'
        end,
    })
end

AddEventHandler('koja-inventory-v2:itemsReady', registerSlots)
CreateThread(registerSlots)

AddEventHandler('koja-inventory-v2:slotChanged', function(source, key, item)
    if key ~= 'radio' then return end
    TriggerClientEvent('my_radio:client:setEnabled', source, item ~= nil)
end)
```

## Slot fields

| Field | Description |
|---|---|
| `key` | Letters, digits and `_`, up to 32 characters. `backpack`, `vest`, `primary`, `secondary` and `clothing_*` are reserved. Registering the same key again updates the slot. |
| `label` | Shown on the slot. |
| `accepts` | A category string, a list of item names, `{ items = {...}, categories = {...}, check = fn }`, or a function. An item fits if it matches any of them. |
| `group` | Heading in the equipment panel (default `custom`). Translate it with `slotGroups.<group>` in the locale files. |
| `icon` | One of `badge book box briefcase camera card clock flashlight gem glasses hammer headphones heart id key lock map package phone pill radio shield star sword syringe tablet tag wallet watch wrench zap`. |
| `size` | `{ w, h }` tile size in cells (1 to 8). |

Function checks run on the server only, get `(itemName, source, slotKey)` (`source` can be `nil` while a slot is loading) and must return `true`. Errors count as "no". The UI cannot run functions, so it shows those slots as valid drop targets and lets the server decide.

## Exports

| Export | Description |
|---|---|
| `RegisterSlot(def)` | Adds or updates a slot. |
| `RemoveSlot(key)` | Removes a custom slot. Items in it are kept and come back when the slot is registered again. |
| `GetSlotItem(source, key)` | `{ id, name, count, metadata, label, slot }` or `nil`. Works for built-in slots too. |
| `SetSlotItem(source, key, name, count?, metadata?)` | Puts a new item in the slot; the old one moves to the player's storage. Returns `false, reason` for `no_slot`, `invalid_item`, `not_accepted`, `too_heavy` or `no_space`. `SetSlotItem(source, key, nil)` deletes the item in the slot. |
| `GetSlots()` | The registered custom slots. |

## Events

- Server `koja-inventory-v2:slotChanged` with `source, key, item | nil` fires whenever any equipment slot changes: drag and drop, quick move, exports, admin actions.
- The owner also gets the client event `koja-inventory-v2:client:slotChanged` with `key, item | nil`.

Slots are not saved as definitions: register them at start and on `koja-inventory-v2:itemsReady`. If the resource that registered a function slot stops, the slot accepts nothing until it is registered again.
