Inventory Tracking
Track stash inventories in real-time with automatic Discord logging and embed updates. Monitor what your employees take or deposit, directly synced to your Discord server.
Overview
The Inventory Tracking system allows you to monitor any stash inventory on your server. Every time an item is taken or deposited, a log is sent to a Discord channel, and an embed showing the current stash contents can be kept up-to-date automatically.
Tip: You can configure tracked inventories directly from the in-game /ampanel admin panel, or programmatically using the exports below.
Supported Inventory Systems
ox_inventoryFull real-time tracking via native swapItems hook
qb-inventoryPeriodic embed updates (no native hook events)
origen_inventoryCan be added via custom hook (see below)
Custom SystemsExtensible architecture — add your own hooks
How It Works
- Register a Stash: Add a stash ID to the tracked inventories (via
/ampanelor export). - Hook Intercepts: When a player moves items to/from the stash, the inventory hook detects it.
- Discord Log: A log message is sent to the configured Discord channel with player name, item, quantity, and action.
- Embed Update: If configured, an embed showing the full stash contents is updated in real-time.
ox_inventory — Real-Time Hook
With ox_inventory, the system registers a native swapItems hook. Every item movement is intercepted without any polling — logs are instant.
-- Automatically registered when ox_inventory is detected
-- No configuration needed — just ensure ox_inventory starts before activitymanager
-- What gets detected:
-- ✅ Player takes item from stash
-- ✅ Player puts item into stash
-- ✅ Item name, count, player name, Discord IDqb-inventory — Periodic Updates
qb-inventory doesn't expose native hook events for item transfers. The embed showing stash contents is updated periodically instead of in real-time.
Note: If you modify qb-inventory to emit custom events on item transfer, you can add real-time tracking. See the "Adding Custom Hooks" section below.
Server Exports
GetTrackedInventories()
Returns a table of all tracked inventories across all guilds.
local allTracked = exports['activitymanager']:GetTrackedInventories()
for inventoryId, data in pairs(allTracked) do
print("Tracking: " .. inventoryId)
print(" Guild: " .. data.guildId)
print(" Log Channel: " .. (data.logChannelId or "none"))
print(" Embed Channel: " .. (data.embedChannelId or "none"))
endGetTrackedInventoriesByGuild(guildId)
Get all tracked inventories for a specific Discord guild.
local guildInventories = exports['activitymanager']:GetTrackedInventoriesByGuild("123456789012345678")
for _, inv in pairs(guildInventories) do
print("Stash: " .. inv.inventoryId .. " - " .. (inv.label or "No label"))
endAddTrackedInventory(data)
Register a new stash inventory to be tracked. Logs and embeds will start automatically.
exports['activitymanager']:AddTrackedInventory({
inventoryId = "police_evidence_locker", -- Stash ID (must match your inventory system)
guildId = "123456789012345678", -- Discord Guild ID
label = "Evidence Locker", -- Display name (optional)
logChannelId = "987654321098765432", -- Discord channel for action logs
embedChannelId = "987654321098765433" -- Discord channel for content embed (optional)
})RemoveTrackedInventory(inventoryId, guildId)
Stop tracking a stash inventory. Logs and embed updates will stop.
exports['activitymanager']:RemoveTrackedInventory("police_evidence_locker", "123456789012345678")
print("Stash no longer tracked")GetInventoryContents(inventoryId)
Get the current contents of a tracked stash.
local contents = exports['activitymanager']:GetInventoryContents("police_evidence_locker")
if contents then
for slot, item in pairs(contents) do
print(string.format("Slot %d: %s x%d", slot, item.name, item.count))
end
endRefreshInventoryEmbed(inventoryId)
Manually trigger an embed update for a tracked stash.
local success = exports['activitymanager']:RefreshInventoryEmbed("police_evidence_locker")
if success then
print("Embed updated!")
else
print("Stash not found or no embed channel configured")
endNetwork Events
These events are used internally by the /ampanel UI. You can also trigger them from custom scripts.
activitymanager:inventory:getTracked
Request tracked inventories for a guild. Response is sent via activitymanager:inventory:trackedList.
-- Client-side
TriggerServerEvent('activitymanager:inventory:getTracked', guildId)
-- Listen for the response
RegisterNetEvent('activitymanager:inventory:trackedList')
AddEventHandler('activitymanager:inventory:trackedList', function(inventories)
for _, inv in pairs(inventories) do
print(inv.inventoryId .. " - " .. (inv.label or ""))
end
end)activitymanager:inventory:save
Save/register a tracked inventory. Response: activitymanager:inventory:saved.
TriggerServerEvent('activitymanager:inventory:save', {
inventoryId = "bar_storage",
guildId = "123456789012345678",
label = "Bar Storage",
logChannelId = "987654321098765432"
})activitymanager:inventory:delete
Remove a tracked inventory. Response: activitymanager:inventory:deleted.
TriggerServerEvent('activitymanager:inventory:delete', "bar_storage", "123456789012345678")Adding Custom Inventory Hooks
The file sv_inventory_public.lua is not encrypted and fully modifiable. You can add support for any inventory system by following the pattern below.
-- Example: Adding support for a custom inventory system
-- Add this to sv_inventory_public.lua
if GetResourceState('my_custom_inventory') == 'started' then
-- Option 1: If the inventory has hook/callback support
exports.my_custom_inventory:registerHook('onItemMoved', function(payload)
local source = payload.source
local stashId = payload.stashId
local itemName = payload.itemName
local count = payload.count
local action = payload.action -- 'take' or 'put'
ProcessInventoryAction(source, stashId, itemName, count, action)
return true
end)
-- Option 2: If the inventory emits server events
RegisterNetEvent('my_custom_inventory:itemTransferred')
AddEventHandler('my_custom_inventory:itemTransferred', function(data)
ProcessInventoryAction(source, data.stashId, data.itemName, data.count, data.action)
end)
print("^2[ActivityManager Inventory]^7 my_custom_inventory hooks registered")
endInternal: ProcessInventoryAction
This is the core function used internally by all hooks. You can call it directly if you're writing a custom integration.
-- ProcessInventoryAction(source, stashId, itemName, itemCount, actionType)
--
-- source: Player server ID
-- stashId: The inventory/stash identifier (string)
-- itemName: Name of the item moved
-- itemCount: Quantity moved
-- actionType: 'take' or 'put'
-- Example: Manually log a custom action
ProcessInventoryAction(source, "my_stash_id", "water", 5, "put")Note: ProcessInventoryAction is a local function inside sv_inventory_public.lua. To call it from another resource, use the exports or trigger the network events instead.
AMPanel Callbacks
These callbacks are registered automatically and used by the in-game /ampanel UI. You can also use them from custom NUI or client scripts via AMTriggerCallback.
| Callback | Parameters | Description |
|---|---|---|
ampanel:getInventories | guildId | Get all tracked inventories for a guild |
ampanel:saveInventory | data | Save/register a tracked inventory |
ampanel:deleteInventory | guildId, inventoryId | Remove a tracked inventory |
ampanel:getDiscordChannels | guildId | Fetch Discord channels for channel picker |
ampanel:refreshInventoryEmbed | inventoryId | Force-refresh a stash content embed |
Quick Start Example
Complete example: register a stash to be tracked and verify it's working.
-- server-side: Register a stash for tracking
-- (Run this once, e.g., in your job script or via /ampanel)
exports['activitymanager']:AddTrackedInventory({
inventoryId = "mechanic_parts", -- Must match the stash ID in your inventory system
guildId = "123456789012345678", -- Your Discord guild ID
label = "Mechanic Parts Storage", -- Friendly name
logChannelId = "111222333444555666", -- Discord channel for logs
embedChannelId = "111222333444555677" -- Discord channel for live content embed
})
-- That's it! If you're using ox_inventory, every take/put will now:
-- 1. Send a log to your Discord channel
-- 2. Update the content embed automatically
-- To verify it's tracked:
local tracked = exports['activitymanager']:GetTrackedInventories()
if tracked["mechanic_parts"] then
print("✅ mechanic_parts is being tracked!")
end
-- To manually refresh the embed:
exports['activitymanager']:RefreshInventoryEmbed("mechanic_parts")