Resources

REC_ItemManager

Welcome to REC_ItemManager documentation.

Track every item on your server, edit its cost and price, and recount how much of it is actually in circulation.

Category

PAIDESCROWED

Installation

Download the dependencies

GitHub - ox_lib

Download from here!!

Download main resource

Tebex - REC_ItemManager

Download from here!!

Import the SQL file

Run rec_manager_items.sql on your database.

rec_manager_items.sql
  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/itemmanagerhttp://<host>:30120/REC_ItemManager/
AuthenticationACE groupAuthorization: Bearer <adminToken>
DefaultEnabledDisabled
Extra setupNoneSee below
The in-game UI works out of the box and never touches HTTP. If you only use /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

This route is served on the same port your players connect to (30120 by default). That port has to stay open for players, so you cannot protect the panel by closing it — 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.

Adding 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.

If you run a tunnel such as 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

SetupConfigNotes
In-game onlyDefaultsNothing to configure
SSH port forwardenabled = true onlySource is loopback, already allowed
Tailscale / WireGuardAdd 100.64.0.0/10Or the single device address
Same LANAdd your LAN rangee.g. 192.168.1.0/24
Reverse proxy + HTTPStrustedProxies = { "127.0.0.1" }Authentication must be set on the proxy
The browser route speaks plain HTTP — FiveM cannot terminate TLS. The token travels unencrypted, so only use this route inside an encrypted path (VPN, SSH tunnel, or a TLS reverse proxy).

Command

CommandPermissionDescription
/itemmanagerconfig.web.aceGroupsOpens the admin UI in-game

Exports

getCost

Returns the cost of an item.

ArgumentTypeDescription
itemNamestringItem 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.

ArgumentTypeDescription
itemNamestringItem 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>:

FieldTypeDescription
namestringItem name
typeItemTypesgeneral or weapon
labelstringDisplay label
imagestringFile name under ox_inventory/web/images
costintegerCost
priceintegerPrice
circulationintegerAmount in circulation
updatedAtinteger?Last updated
createdAtinteger?Created
exports.REC_ItemManager:getItems()
All three read the cache built at startup. The database is only touched when the resource starts, when you save a cost / price, and when you press Recount circulation.

Stracture

config/sv_config.lua
  ---@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
©2026 RE:CORD Development Group. All rights reserved.