REC_ItemManager
Track every item on your server, edit its cost and price, and recount how much of it is actually in circulation.
Category
PAIDESCROWEDInstallation
Download the dependencies
Download main resource
Import the SQL file
Run rec_manager_items.sql on your database.
CREATE TABLE IF NOT EXISTS `rec_manager_items` (
`id` bigint(8) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL,
`cost` bigint(8) NOT NULL,
`circulation` bigint(20) NOT NULL,
`price` bigint(8) NOT NULL,
`updatedAt` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
`createdAt` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=319 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_general_ci;
Copy and paste this on your server.cfg
# ox
ensure ox_lib
ensure oxmysql
# RE:CORD
ensure REC_Library
ensure REC_Utils
ensure REC_PlayerManager
ensure REC_ItemManager
Give yourself the ACE permission
add_ace group.admin REC_ItemManager.command allow
add_principal identifier.fivem:1 group.admin
Done
enjoy it !
Admin UI
The panel can be opened in two ways. Both show the same build, but they are authenticated differently.
| In-game (NUI) | Browser | |
|---|---|---|
| How to open | /itemmanager | http://<host>:30120/REC_ItemManager/ |
| Authentication | ACE group | Authorization: Bearer <adminToken> |
| Default | Enabled | Disabled |
| Extra setup | None | See below |
/itemmanager, you can skip the whole browser section.config.web.aceGroups is a list — a player passes when they hold any one of them.
config.web = {
---@type string[]
aceGroups = {
"admin",
-- "moderator",
},
}
Browser route
allowedAddresses (this machine only by default) and the token are all that stand between the panel and the internet.Reach the panel from an address you have allowed. Any one of these works:
- Reach it over a VPN (Tailscale, WireGuard)
- Reach it through an SSH port forward
- Put a TLS reverse proxy in front of it (see
trustedProxies) - Only reach it from the same LAN
If you are on shared hosting and can do none of these, leave the browser route disabled and use the in-game UI.
Enabling it
config.web.http.enabled = true
set REC_ItemManager:adminToken "a-random-string-of-32-chars-or-more"
48 characters, generated in your browser. Nothing is sent anywhere.
An empty token makes every /api/* call return 401. A token shorter than 32 characters prints a warning on startup.
allowedAddresses
Requests from an address that is not on this list get 404 — before authentication, so the route stays invisible from the outside. The default allows this machine only.
allowedAddresses = {
"127.0.0.0/8", -- loopback
"::1",
-- "100.64.0.0/10", -- Tailscale (CGNAT range)
-- "192.168.1.0/24", -- LAN
-- "172.18.0.0/16", -- Docker bridge
}
Entries are IPv4 CIDR blocks or literal addresses. IPv6 has no CIDR support, so write the address in lowercase canonical form. An entry that can never match is reported on startup.
0.0.0.0/0 publishes the panel to the entire internet, leaving the token as the only protection.trustedProxies
Behind a reverse proxy every request arrives from the proxy itself, so the address check can no longer see the real client. Register the proxy instead:
trustedProxies = {
"127.0.0.1",
}
Requests from a registered proxy skip the address check, which means access control becomes the proxy's job — configure authentication there (Caddy basic_auth, nginx allow / deny, Cloudflare Access). X-Forwarded-For is only written to the debug log, never used to authorise, because any client can forge it.
cloudflared or ngrok on the same host, every visitor arrives as 127.0.0.1. The default loopback entry then no longer means "this machine only" — add authentication at the tunnel.Common setups
| Setup | Config | Notes |
|---|---|---|
| In-game only | Defaults | Nothing to configure |
| SSH port forward | enabled = true only | Source is loopback, already allowed |
| Tailscale / WireGuard | Add 100.64.0.0/10 | Or the single device address |
| Same LAN | Add your LAN range | e.g. 192.168.1.0/24 |
| Reverse proxy + HTTPS | trustedProxies = { "127.0.0.1" } | Authentication must be set on the proxy |
Command
| Command | Permission | Description |
|---|---|---|
/itemmanager | config.web.aceGroups | Opens the admin UI in-game |
Exports
getCost
Returns the cost of an item.
| Argument | Type | Description |
|---|---|---|
itemName | string | Item name to look up |
Returns integer? — nil when the item is not registered.
exports.REC_ItemManager:getCost(itemName)
getPrice
Returns the price of an item.
| Argument | Type | Description |
|---|---|---|
itemName | string | Item name to look up |
Returns integer? — nil when the item is not registered.
exports.REC_ItemManager:getPrice(itemName)
getItems
Returns every item's metadata, keyed by item name.
Takes no arguments. Returns table<string, MetaData>:
| Field | Type | Description |
|---|---|---|
name | string | Item name |
type | ItemTypes | general or weapon |
label | string | Display label |
image | string | File name under ox_inventory/web/images |
cost | integer | Cost |
price | integer | Price |
circulation | integer | Amount in circulation |
updatedAt | integer? | Last updated |
createdAt | integer? | Created |
exports.REC_ItemManager:getItems()
Stracture
---@class REC_ItemManager.Server.Config
local config = {}
---[[
--- Default cost of an item
---]]
---@type integer
config.defaultCost = 10000
---[[
--- Default price of an item
---]]
---@type integer
config.defaultPrice = 20000
---[[
--- Only count players who logged out within this many days
---]]
---@type integer
config.targetPlayerLastLoginDays = 3
---[[
--- Set this when cash is handled as an inventory item
---]]
config.cash = {
---@type boolean
enabled = true,
---@type string
itemName = "money",
}
---[[
--- Citizen ids to leave out of the circulation count
---]]
---@type string[]
config.ignoreCountCitizenIds = {
-- "OJLN42H4",
}
---[[
--- Admin UI
--- The in-game UI (/itemmanager) is always available and is gated by aceGroups.
--- config.web.http below only affects the browser route.
---]]
config.web = {
---@type string[]
aceGroups = {
"admin",
},
http = {
---@type boolean
enabled = false,
---@type string
token = GetConvar(GetCurrentResourceName() .. ":adminToken", ""),
---@type string
distDir = "web/build",
---@type string[]
allowedAddresses = {
"127.0.0.0/8",
"::1",
},
---@type string[]
trustedProxies = {
},
images = {
---@type string
resource = "ox_inventory",
---@type string
dir = "web/images",
},
},
}
---[[
--- Debug Mode
---]]
---@type boolean
config.debugMode = true
return config