Items & Metadata
Items & Metadata
Items are defined once, in data/items.lua, and read by both sides. The NUI never invents item data and never trusts what the client sends.
Definition fields
weapon_pistol = {
label = 'Pistol',
weight = 1100, -- ALWAYS grams
stack = false, -- one per slot
category = 'weapon',
rarity = 'rare',
description = 'Standard semi-automatic pistol.',
durability = true,
}
| Field | Meaning |
|---|---|
label |
Display name |
weight |
Grams. The server computes in grams everywhere |
stack |
false never merges · absent uses Config.UI.maxStack · N caps at exactly N. false is for items that must be told apart — a weapon, a container, anything with durability — not a way to say rare |
category |
weapon ammo consumable medical tool clothing valuable document container misc |
rarity |
common uncommon rare epic legendary — visual only |
description |
One line in the detail column |
image |
Override; defaults to images/<name>.webp, then .png (see Images) |
usable |
Override for whether "Use" appears |
durability |
Created at 100 and wears down |
effect |
What using it does — see Consumables |
cooldown |
ms before this item can be used again |
useTime |
ms one use takes. The portion is spent when it finishes |
portions |
How many uses one unit is worth. Read only while Config.Portions.enabled is on (off by default, as ox_inventory) |
cancel |
true = the action bar can be cancelled with the cancel key; absent = it cannot (ox client.cancel) |
disable |
{ move, sprint, car, combat } the player cannot do while using it; combat is off during every use unless set false (ox client.disable) |
close |
false = using it leaves the inventory open; absent = it closes (ox close) |
Serial numbers
Any item that cannot stack gets a ULID when it is created — a sortable, unique id that stays with that specific unit for its whole life.
invtrace <uid>
prints where it was born, every hand it passed through, and where it is now.
Metadata
Beyond durability and serials, a unit can carry arbitrary metadata: a weapon's registered owner and attachments, a note's text, a licence's issue date.
exports.aty_inventory:SetMetadata(inv, slot, {
registered = 'John Miller',
components = { 'suppressor', 'flashlight' },
})
Metadata travels with the unit through every move, and differing metadata is what keeps two otherwise-identical items in separate slots.
Where definitions can come from
| Source | When |
|---|---|
data/items.lua |
The resource's own list — the file a developer edits |
| The framework | QBCore / ESX shared items, read through the bridge |
| Admin panel → Definitions | Live field edits, stored in aty_overrides |
A panel edit is an override on a single field, not a second catalogue. Nothing there ever writes to data/items.lua, and the panel shows the file value beside the live one — the moment two records disagree without showing it, nobody can say which one the server is running.
Framework items work as they are. invmigrate items write writes them to data/items.generated.lua; list that file in fxmanifest.lua's shared_scripts after data/items.lua and this resource owns them, no longer depending on the framework being up. Items already in data/items.lua are skipped — the file here wins.
An adopted item is one-per-slot only when its own catalogue says so — qb's unique, ox's stack = false. qb's rare is about value, not storage, and is not read as uniqueness. Items already stored under an older rule are brought up to date with invrestack confirm, which repairs loaded bags as well as stored ones.
Images
Drop a WebP or PNG named after the item into ui/images/. The interface tries, in order:
- the definition's
image images/<name>.webp, thenimages/<name>.png- each entry of
Config.UI.imageFallback— another resource's art,%sstanding for the item name - a placeholder
Config.UI.imageFallback = {
'nui://qb-inventory/html/images/%s.png',
'nui://ox_inventory/web/images/%s.png',
}
Adopting a framework's catalogue adopts hundreds of names and none of their art; the inventory they came from usually still holds the pictures. Point the list at wherever that folder lives on your server. A file in ui/images/ always wins, because it is tried first.
scripts/collect-item-images.py copies matching pictures from the rest of the server into ui/images/.
invdoctor
reports how many items have no picture and writes the full list to data/missing-images.txt.