One Studios
Server

Hooks

Cancellable beforeX hooks.

Hooks let you gate an inventory action before it happens. Register a callback for a beforeX event, inspect the payload, optionally mutate it in place, and return false to cancel.

local id = exports.one_inventory:RegisterHook('beforeItemAdd', function(payload)
    if payload.item == 'lockpick' and not isAllowed(payload.source) then
        return false  -- cancels the AddItem call
    end
    -- mutating payload.metadata is honored; not every field is (see each hook below)
    payload.metadata = payload.metadata or {}
    payload.metadata.stamped = true
end)

-- later
exports.one_inventory:RemoveHook(id)
Returning false cancels the operation. Returning true, nil or nothing allows it. Callbacks run in registration order. Errors are caught and logged, they do not abort the chain. Only some payload fields feed back into the action once mutated. This is noted per-field below where it applies.
For post-action notifications ("an item was added"), use Events instead.

Post-hook events

Every RegisterHook registration gets its own post-hook event, fired once the hooked action finishes:

local id = exports.one_inventory:RegisterHook('beforeItemAdd', function(payload)
    if payload.item == 'lockpick' then return false end
end)

AddEventHandler(('one_inventory:onHookHandled:%d'):format(id), function(success, payload)
    -- success is false when a hook (yours or another resource's) cancelled the action
end)
success
boolean
false when a hook cancelled the action (yours or any other resource's registered on the same event).
payload
table
The same payload the pre-hook callback received.
fn may be nil when you pass an options table, for a post-events-only registration: you get the post event without ever gating the action. RemoveHook is scoped to the calling resource, and passing no id removes every hook that resource registered. Hooks are also auto-removed when the registering resource stops.

Exports

See RegisterHook and RemoveHook for the export signatures.

Filters

RegisterHook's third argument, options, narrows a hook so it only fires (and only sends a post event) for the cases you actually care about, instead of every single beforeX in the game. All filters you set must pass (AND-combined, not OR).

exports.one_inventory:RegisterHook('beforeItemAdd', function(payload)
    if payload.item == 'lockpick' then return false end
end, {
    itemFilter = { lockpick = true, weapon_pistol = true },  -- only these two items
    inventoryFilter = { '^stash:' },                          -- only stashes
    typeFilter = { stash = true },                             -- only inventoryType == 'stash'
    print = true,                                              -- log every match to console
})
options.print
boolean
Logs every invocation of this hook to console. Handy while you're figuring out whether your filters actually match.
options.itemFilter
table<string, boolean>
A set of item names to match, e.g. { lockpick = true, weapon_pistol = true }. Without it, the hook fires for every item. Matches on either side of a swap (both the item moving and, if present, the item it swaps with).
options.inventoryFilter
string[]
An array of Lua patterns matched against the inventory reference(s), e.g. { '^stash:' } to only match stashes, or { '^123$' } to only match player server id 123. A player inventory reference is its server id, converted to a string before matching.
options.typeFilter
table<string, boolean>
A set of inventory types to match, e.g. { stash = true, trunk = true }. Matches the resolved inventory type (player, stash, trunk, glovebox, container, drop, dumpster, or, for shop hooks, the specific shop's name).
Not every hook sets an inventory or type reference. beforeItemRemove, beforeItemUse, and beforeInventoryClose never set a type field, so a typeFilter on those never matches. beforeItemGive sets no inventory reference at all, so inventoryFilter never matches it. For shop hooks, the type field holds the specific shop's name, not a generic "shop" category, so typeFilter targets one shop at a time.
Any inventory-reference field (inventoryId, fromInventory, toInventory) is string | number: a number (the player's server id) when the inventory belongs to a player, otherwise the internal string id (stash:<name>, trunk:<plate>, ...).

beforeItemAdd

Fires before an item is added to an inventory. Cancel to block the add.

payload.source
number | nil
Server id of the player, or nil for non-player inventories.
payload.inventoryId
string | number
Inventory the item is being added to.
payload.item
string
Item name being added.
payload.count
number
Count being added. Read-only: mutating it has no effect on how many items actually get added.
payload.metadata
table | nil
Mutable. A callback may replace or mutate payload.metadata; the add uses the mutated value.
payload.slot
number | nil
Slot to force the item into, when the caller specified one.
payload.inventoryType
string
Resolved inventory type (player, stash, trunk, ...).
payload.itemDef
table | nil
The resolved item definition, or nil if the item is unknown.

beforeItemRemove

Fires before an item is removed from an inventory. Cancel to block the removal.

payload.source
number | nil
Server id of the player, or nil for non-player inventories.
payload.inventoryId
string | number
Inventory the item is being removed from.
payload.item
string
Item name being removed.
payload.count
number
Count being removed. Read-only: unlike beforeItemAdd, mutating count or metadata here has no effect on the actual removal.
payload.metadata
table | nil
Metadata filter used to match which slot(s) to remove from.
payload.slot
number | nil
Slot to restrict removal to, when the caller specified one.

beforeItemUse

Fires before a player uses or consumes an item. Cancel to block the use.

payload.source
number
Player using the item.
payload.item
string
Item name.
payload.slot
number
Slot the item is being used from.
payload.metadata
table | nil
Live reference to the item's metadata. Mutating a field on it in place affects the real item; reassigning payload.metadata to a new table does not.
payload.inventoryId
string | number
Inventory holding the used item. Not necessarily the player: can be an open container.
payload.itemTable
table
The full slot item (name, count, slot, metadata).
payload.consume
number | nil
The item's configured consume value.

beforeItemSwap

Fires before an item swap between slots or between inventories. Cancel to block the swap.

payload.source
number | nil
The acting player who triggered the move. nil for a programmatic move, e.g. one made through the SwapSlots export.
payload.fromInventory
string | number
Source inventory id.
payload.toInventory
string | number
Destination inventory id.
payload.fromSlot
number
Source slot index.
payload.toSlot
number
Destination slot index.
payload.count
number
Read-only: mutating it does not change how many items actually move.
payload.item
string
Name of the item being moved.
payload.fromType
string
Type of the source inventory.
payload.toType
string
Type of the destination inventory.
payload.fromItem
table
The full source slot item.
payload.toItem
table | nil
The full destination slot item, or nil when the target slot is empty (a move).
payload.action
string
One of move (target empty), stack (same stackable item), or swap (different items).
source identifies the acting player, not "which inventory changed". To determine that, read the per-side fromInventory / toInventory and fromType / toType instead: source can be nil for programmatic moves, and for a transfer triggered by another player it identifies that player rather than either side of the swap.

beforeShopPurchase

Fires before a shop purchase is finalised. Cancel to block the sale.

payload.source
number
Buyer's server id.
payload.shop
string
Shop name.
payload.item
string
Item name being purchased.
payload.count
number
Count being purchased.
payload.currency
string
Currency used (cash, black_money, custom).
payload.price
number
Total price (unitPrice * count).
payload.shopId
string
Same value as shop.
payload.shopType
string
Same value as shopId.
payload.toInventory
string | number
Buyer's inventory id.
payload.toSlot
number | nil
Target slot in the buyer's inventory.
payload.fromSlot
table
The shop slot being purchased from (a slot table, not a slot number).
payload.itemName
string
Same value as item.
payload.metadata
table | nil
payload.unitPrice
number
Per-unit price after dynamic pricing.

beforeShopOpen

Fires before a shop UI opens. Cancel to block the open.

payload.source
number
Player opening the shop.
payload.shopId
string
payload.shopType
string
Same value as shopId.
payload.label
string
Shop label.
payload.slots
number
Shop slot count.
payload.items
table
The shop's slot table.
payload.currency
string
Shop currency.

beforeItemCraft

Fires before a craft begins. Cancel to block the craft.

payload.source
number
Player crafting.
payload.bench
string
Crafting bench name.
payload.item
string
Output item.
payload.count
number
Output count.
payload.metadata
table | nil
Read-only: the crafted item's actual metadata is cloned fresh from the recipe, not from this field, so mutating it here has no effect.
payload.benchId
string
Same value as bench.
payload.benchIndex
number
Recipe slot index on the bench.
payload.recipe
table
The recipe slot.
payload.toInventory
string | number | nil
Crafter's inventory id.
payload.toSlot
nil
Always nil at fire time (the craft hasn't landed a slot yet).

beforeItemDrop

Fires before an item is dropped on the ground. Cancel to block the drop.

payload.source
number
Player dropping the item.
payload.item
string
Item name being dropped.
payload.count
number
Count being dropped.
payload.fromInventory
string | number
Inventory the item is being dropped from.
payload.fromSlot
number
Source slot index.
payload.fromType
string
Type of the source inventory.
payload.fromItem
table
The full source slot item.

beforeItemGive

Fires before a player-to-player item transfer. Cancel to block the give.

payload.source
number
The giver.
payload.targetSource
number
The receiver.
payload.item
string
Item name being given.
payload.count
number
Count being given.
payload.fromSlot
number
Giver's source slot index.
payload.fromContainerId
string | nil
Set when the give originated from an open container instead of the player's main inventory.
This payload is a fresh table built just for the hook call, not a live reference to anything: mutating any field here has no effect on the give, or on the later onItemGiven event (which builds its own separate payload).

beforeItemSearch

Fires before a player frisks another player's inventory. Cancel to block the search.

payload.source
number
The searcher.
payload.targetSource
number
The player being searched.

beforeInventoryOpen

Fires before any inventory UI opens. Cancel to block the open.

payload.source
number
Player opening the inventory.
payload.data
table | nil
Raw, type-specific request from the client (stash name, plate, drop id, ...). nil when opening the player's own inventory.
payload.inventoryId
string | number
Identifier taken straight from data for the resolved type (e.g. the raw stash/shop/bench name, the drop or dumpster id, the vehicle plate, or the target's server id for player). Unlike other hooks' inventoryId, this is the client's raw request value, not a resolved inventory reference.
payload.inventoryType
string
Resolved inventory type: player, stash, shop, crafting, drop, dumpster, trunk, or glovebox.
payload.slot
number | nil
Slot, when present in data.
payload.netId
number | nil
Vehicle net id, when present in data.

beforeInventoryClose

Fires before an inventory UI closes. Cancel to keep it open.

payload.source
number
Player closing the inventory.
payload.data
table
Whatever secondary-inventory data was open, same shape as beforeInventoryOpen's data.