Skip to content
Blog

Serve ox_inventory item images from a CDN

Move ox_inventory’s web/images to a CDN with one zip and one convar: a lighter resource, faster joins, and item images you can replace without a restart.

By Termi

7 min read
  • ox_inventory
  • CDN
  • Performance
On this page

Every item in ox_inventory has a PNG in ox_inventory/web/images. On a server with a few hundred custom items that folder easily grows to tens of megabytes, and all of it is part of a resource that every player downloads before they can play. This guide moves the images to a CDN: one zip upload, one line in server.cfg, and the inventory loads its images from cdn.fivedock.dev instead.

Why move the images out of the resource

ox_inventory’s fxmanifest.lua lists web/images/*.png under files, which makes every image a resource file: the server sends it to each client, and the client keeps it in its FiveM cache. That has a few costs:

  • A new player downloads the whole folder before spawning, including images of items they will never hold, so joining takes longer.
  • FiveM caches resource files by hash, so an unchanged image is not fetched twice. But every image you add or replace is one more file every player downloads on their next join.
  • A new file in the resource only reaches players after ox_inventory restarts, and on a live server that is the one resource you least want to restart.

With a CDN the images are not part of the resource at all. The inventory’s browser page asks for an image only when it has to show that item, and keeps it in its HTTP cache for next time. A player who never opens the gun shop never downloads the gun images.

How ox_inventory finds an image

ox_inventory reads the replicated convar inventory:imagepath and builds each image link as <imagepath>/<item name>.png. The default points into the resource itself:

image links
default  nui://ox_inventory/web/images/burger.png
CDN      https://cdn.fivedock.dev/o/<your-workspace>/items/burger.png

So all it needs is a folder on the web where every image is named exactly like its item. Two details from the ox_inventory source:

  • The extension is always .png. Keep the images as PNG files named after the item: burger.png, not Burger.PNG or burger.webp.
  • An item’s own client.image still wins. A full URL there is used as it is; a bare file name such as burger_deluxe.png is looked up under the imagepath, so it moves to the CDN with everything else.

Upload the images

On FiveDock, a file uploaded with a key gets a link that never changes: https://cdn.fivedock.dev/o/<your-workspace>/<key>. Upload the images so that burger.png gets the key items/burger.png, and the imagepath is …/items. Your workspace slug is the short name shown in the panel, and it is also your S3 bucket name.

From the panel

  1. Zip what is inside the folder, not the folder itself: on Windows, open ox_inventory/web/images, select every file with Ctrl+A, right-click and pick Send to → Compressed (zipped) folder, or run zip -r ../images.zip . inside ox_inventory/web/images.
  2. In the panel, open Files, create a folder called items and open it.
  3. Click Upload and drop the zip. Keep file names is on by default for archives; leave it on.

FiveDock unpacks the archive and stores each image as its own file. Folders inside the archive are kept, which is why you zip the images and not the images folder: burger.png becomes items/burger.png, not items/images/burger.png. Anything that is not an accepted image, like a stray readme.txt, is skipped and listed in the result.

Over the API

The same in one request, with an API token (fd_live_…, made on the Tokens page) that has the files:write scope:

bash
cd ox_inventory/web/images
zip -r ../images.zip .

curl -X POST https://api.fivedock.dev/api/v3/file/archive \
  -H "Authorization: Bearer fd_live_…" \
  -F "file=@../images.zip" \
  -F "path=items"
response
{
  "status": "ok",
  "data": {
    "uploaded": [
      {
        "key": "items/burger.png",
        "url": "https://cdn.fivedock.dev/o/my-city/items/burger.png",
        "fileUrl": "https://cdn.fivedock.dev/f/…png",
        "filename": "burger.png",
        "path": "/items",
        "size": 18233,
        "mime_type": "image/png",
        "deduplicated": false
      }
    ],
    "skipped": [{ "name": "readme.txt", "reason": "unsupported_type" }]
  }
}

url is the stable keyed link; fileUrl is the per-upload link that every file also gets. One archive can hold up to 5,000 files, which covers nearly any item list. If yours is bigger, upload it in two archives or use rclone.

With rclone (S3)

If you keep your server in Git or deploy with scripts, the S3 endpoint is the most comfortable route. Set up an rclone remote called fivedock as shown in the upload docs, then:

rclone
# my-city = your workspace slug (it is also your S3 bucket name)
rclone sync ./ox_inventory/web/images fivedock:my-city/items --dry-run
rclone sync ./ox_inventory/web/images fivedock:my-city/items

rclone sync makes the destination match the source, and that includes deleting files in items that are not in your local folder. Run it with --dry-run first, or use rclone copy if you only ever add and update.

Objects you put over S3 are keyed files too, so they get the same stable links.

Point ox_inventory at the CDN

Add the convar to server.cfg, above the line that starts ox_inventory:

server.cfg
setr inventory:imagepath "https://cdn.fivedock.dev/o/<your-workspace>/items"

ensure ox_inventory
  • Use setr, not set: the inventory UI runs on the client, and only setr convars are replicated to clients.
  • No trailing slash, ox_inventory adds it.
  • ox_inventory reads the convar when it starts, so restart it (or the server) once.

Before you restart, open one image in your browser to check the link, for example https://cdn.fivedock.dev/o/<your-workspace>/items/burger.png.

Take the images out of the resource

Join the server, open your inventory and a shop or two, and check that the images load. Once they do, move the PNG files out of ox_inventory/web/images and keep them somewhere else: that folder is now the source for your uploads, not part of the server.

When you update ox_inventory later, leave web/images out of the copy, or empty it again afterwards.

Adding and replacing images later

To add an image for a new item, or fix an old one, upload the PNG with the key it should have: Upload in the items folder of the panel with Keep file names on, or the API:

bash
curl -X POST https://api.fivedock.dev/api/v3/file \
  -H "Authorization: Bearer fd_live_…" \
  -F "[email protected]" \
  -F "key=items/burger.png"

If items/burger.png already exists, the upload replaces it behind the same link; the old version is kept for 7 days before it is purged. Keyed links are cached for 5 minutes and then revalidated, so players see the new image within about five minutes, with no restart and nothing new to download when they join.

If an image does not show

  • One item has a broken image. The file name must match the item name exactly, including case: WEAPON_PISTOL.png for WEAPON_PISTOL. Keys built from file names turn spaces into - and other unusual characters into _, so an image called my item.png ends up as my-item.png.
  • Every image is broken. Open the imagepath plus /burger.png in a browser. A 404 usually means the folder is off (items/images/burger.png instead of items/burger.png) or the workspace slug is wrong. Also check that you used setr.
  • The old image is still there after a replace. Give it five minutes; players keep the previous copy until their cache expires.
  • An item ignores the CDN. It probably sets client.image to a full URL. Remove it, or point it at the CDN.

The same keyed links work for any other image your UI loads, such as vehicle thumbnails or mugshots: upload once and use the link wherever you need it.

Common questions

Where does ox_inventory load item images from?

From the replicated convar inventory:imagepath, which defaults to nui://ox_inventory/web/images, the web/images folder inside the resource. For each item, ox_inventory builds the link <imagepath>/<item name>.png. An item’s own client.image overrides that: a full URL is used as it is, and a bare file name is looked up under the imagepath.

How do I change the image path in ox_inventory?

Add setr inventory:imagepath "https://cdn.fivedock.dev/o/<your-workspace>/items" to server.cfg above ensure ox_inventory, with no slash at the end, and restart ox_inventory once. It has to be setr, because the inventory UI runs on the client and only setr convars reach clients. Every image must then sit at that address, named exactly like its item, with a .png extension.

Why are my ox_inventory item images not showing?

Usually the file name does not match the item name exactly, including upper and lower case, or the file is not a .png. If every image is missing, open the imagepath plus /burger.png in a browser: a 404 means the folder or the workspace slug in the path is wrong. Also check that the convar was set with setr, not set.

Can ox_inventory use WebP or JPG item images?

Only item by item. The imagepath lookup always adds .png to the item name, so a burger.webp next to it is never requested. Set the item’s client.image to burger.webp, which is looked up under the imagepath, or to a full URL, and the inventory’s NUI page loads it like any browser would.

Do I have to restart ox_inventory to change an item image?

Not when the images are on a CDN. Upload the new PNG to the same key, such as items/burger.png, and players see it within about five minutes, once their cached copy expires. Images inside web/images only reach players after ox_inventory restarts.

All articles