An admin item spawner for ox_inventory. Every registered item in one searchable grid — weapons, ammo, attachments and all — with per-item quantities, a metadata editor, saved loadouts, and delivery to yourself, one player, everyone nearby or the whole server. One command in, everything else server-validated.
If sd-itemspawner is useful to you, please ⭐ the repo. Issues and pull requests are always welcome.
Important
sd-itemspawner is ox_inventory-only, by design.
There is no inventory bridge and no framework detection. It calls ox_inventory's exports
directly and resolves players through natives, which is why it needs no framework, no database
and no SQL file. If you run any other inventory, this resource is not for you.
A spawner that shows you everything ox_inventory actually has registered — not a hand-written
list you have to keep in sync. The catalog is read from ox_inventory:Items() at runtime, so it
includes data/items.lua, data/weapons.lua, and anything another resource registered on boot.
Add an item to items.lua and it is in the grid on the next restart with no edits here.
| Catalog | Every registered item, auto-categorised into weapons / ammo / attachments / usable / misc. Icons resolve from ox_inventory's own image folder — nothing is copied or duplicated. |
| Search | Multi-term and ranked. Typing pistol puts WEAPON_PISTOL first instead of burying it under thirty attachments that merely contain the word. |
| Quantities | Per item, not one global amount. Stage 5x bandage and 1x pistol in the same spawn. |
| Metadata | Free-form key/value editor per item, with the value type inferred and shown as a badge. Leave it empty and ox_inventory generates its own serials and durability. |
| Loadouts | Save a selection by name, re-spawn it in one click, or load it back into the tray to edit first. Stored per admin in a flat JSON file — no database table. |
| Targets | Yourself, a specific player, everyone within a radius, or every connected player. |
| Feedback | Partial success is reported as partial success: "delivered 4, 1 failed — Riley Bennett: Gold Bar (no room)", not a silent failure. |
The whole design assumes an attacker can call any server callback with any payload, because they can. Nothing is protected by being hard to find.
Getting in. lib.addCommand registers /itemspawner as a restricted command, so FiveM's own
server-side dispatcher checks the command.itemspawner ACE before the handler runs. That handler
is the only writer of the in-memory session table.
Staying in. Every privileged callback then runs four checks, cheapest first:
| Check | Rejects |
|---|---|
| A session exists | Anyone who never ran the command |
| The ACE still holds, re-tested per call | An admin whose permission was pulled mid-session |
| The identifier still matches | Server-ID reuse — a player disconnecting and the next joiner inheriting their slot and their session |
| The session is inside its TTL | A panel left open and walked away from |
What gets trusted. Nothing the client says about an item. It sends a bare item key; the server
resolves it against its own catalog and reads the label, weight and stack from there. Unknown keys,
blacklisted items, non-integer counts, counts over the cap, oversized or too-deeply-nested metadata,
and offline targets are all rejected before anything reaches AddItem. Counts above the cap are
rejected rather than clamped, so a malformed request is visible instead of silently shrunk.
There is one lib.callback.register call site in the resource, and it is inside the wrapper that
applies the gate — so no endpoint can be added later that forgets it. There are no server-side
RegisterNetEvents at all. Every spawn is written to the console and to lib.logger with the
acting admin's name, licence, items and recipients.
/itemspawner ──▶ lib.addCommand (ACE gate) ──▶ session granted ──▶ open event to that client
│
client builds the grid from its OWN ox_inventory registry ◀────────────────┘
│
└─▶ NUI action ──▶ lib.callback ──▶ session check ──▶ validate ──▶ ox_inventory:AddItem
The item catalog is never sent over the network. ox_inventory maintains a registry on both sides, so the client builds the grid from its own copy while the server keeps an independent one purely as the validation authority. Opening the panel costs one small round trip for presets and the player list, not a 500-item payload.
The grid is row-windowed, so a 500-item catalog mounts about thirty cards at a time instead of firing 500 image requests on open.
fxmanifest.lua
configs/config.lua command name, locale, icon path (client-safe)
configs/server/security.lua ACE group, caps, rate limit, blacklist (never sent to clients)
shared/catalog.lua ox_inventory registry -> categorised catalog (both sides)
shared/log.lua tagged console logger
client/main.lua locale + module wiring
client/spawner/nui.lua open event, NUI callbacks, focus
server/main.lua locale + module wiring
server/spawner/init.lua the command and the six guarded callbacks
server/spawner/session.lua the grant table and its four-check gate
server/spawner/validate.lua every client payload, rebuilt from scratch
server/spawner/give.lua target resolution and delivery
server/spawner/presets.lua per-admin loadout store (data/presets.json)
server/spawner/audit.lua console + lib.logger trail
web/ the NUI (React + TypeScript + Vite, prebuilt)
| Resource | What it is for |
|---|---|
| ox_inventory | Required. The item registry and AddItem. |
| ox_lib | Shared library — commands, callbacks, locale, notifications |
sd-itemspawner does not touch the database and has no SQL of its own.
Grab sd-itemspawner-vX.Y.Z.zip from the
latest release and extract
it into your resources folder. The zip contains the prebuilt NUI, so there is no build step.
Note
Don't clone or "Download ZIP" from the repo unless you intend to work on the UI. The compiled
interface is not committed — it is built fresh by CI and attached to each release — so a source
checkout has an empty web/build and the panel will not render. See
Building the UI if that is what you want.
ensure ox_lib
ensure ox_inventory
ensure sd-itemspawnerlib.addCommand creates the command.itemspawner ACE for group.admin automatically, but nobody
is in that group by default. Add yourself in server.cfg:
add_principal identifier.license:YOUR_LICENSE_HERE group.adminTip
Find your licence with status in the server console while connected, or check the
players.json/txAdmin player list. It looks like license:1a2b3c4d....
Already using group.admin for other things? Point the resource at a group of its own instead —
change Group in configs/server/security.lua to e.g. group.spawner, then grant it:
add_ace group.spawner command.itemspawner allow
add_principal identifier.license:YOUR_LICENSE_HERE group.spawner/itemspawner (or /spawner)
Escape closes the panel.
configs/config.lua — client-safe. Command name (which also names the ACE), its alias, the
locale, and the ox_inventory image path.
configs/server/security.lua — deliberately excluded from the manifest's files{} block, so
it is never sent to a client:
| Setting | Default | What it does |
|---|---|---|
Group |
group.admin |
ACE group handed to lib.addCommand |
SessionTTL |
1800 |
Seconds a session stays valid; false to never expire |
MaxCount |
500 |
Hard ceiling on a single item's amount |
MaxSelection |
64 |
Most items in one spawn |
Metadata |
16 keys / depth 2 / 128 chars | Bounds on client-supplied metadata |
RateLimit |
30 burst, 0.5/s | Token bucket per admin |
NearbyRadius |
20.0 |
Metres for the "nearby" target |
Blacklist |
empty | Items nobody can spawn, even with the ACE |
MaxPresets |
40 |
Saved loadouts per admin |
Logs |
on | Console line and lib.logger entry per spawn |
Needed if you cloned the repo rather than downloading a release, or if you are changing the
interface. web/build is not committed.
cd web
npm ci
npm run buildOutput lands in web/build with unhashed filenames to match the manifest's files{} globs.
npm run dev runs the panel in a browser against a generated mock catalog, so you can work on it
without launching the game.
Publishing a GitHub Release triggers .github/workflows/release.yml, which checks out the tag,
builds the NUI from source, stages an allowlist of runtime files into a correctly-named resource
folder, cross-checks that staging against every path fxmanifest.lua declares, and attaches
sd-itemspawner-<tag>.zip to the release. workflow_dispatch re-packages an existing tag without
unpublishing it.
Every push and pull request runs .github/workflows/ci.yml: tsc --noEmit, a production build,
and a Lua syntax check across every .lua file outside web/.
Built by Samuel Nicol. Licensed under GPL-3.0.