FiveM Docs
Core Resources

esx_inventory

esx_inventory is the default ESX Legacy NUI inventory. Learn its config, keybinds, the server storage API exports and how it relates to ox_inventory.

esx_inventory is the default slot-based inventory UI for ESX Legacy. It opens with F2, shows items, money and weapons from es_extended, and offers a small server API so other resources can show a second "storage" panel (stashes, society stock, trunks). es_extended remains the authority on items and weight; this resource is the presentation layer.

Overview

FactValue
Resourceesx_inventory
SideClient UI (React NUI) + server storage bridge
Dependencieses_extended, esx_lib
DatabaseNone (slot layout is stored in client KVP esx_inventory:slots)
Version1.16.3 (shipped with ESX Legacy 1.16.4)

Features verified in the source:

  • Slot grid with drag and drop. The layout is saved per player with SetResourceKvp.
  • Use, give to a nearby player and drop items, routed through the validated es_extended events (esx:useItem, esx:giveInventoryItem, esx:removeInventoryItem).
  • Give the ammo of a selected weapon to a nearby player.
  • Cash, black money and weapons shown next to items.
  • Hotbar styling on the first slots with keybinds that use the item in that slot.
  • Toasts for gained and lost items, even while the inventory is closed.
  • Optional second panel for storages registered by other resources.

Not compatible with ox_inventory

es_extended checks GetResourceState("ox_inventory") ~= "missing" and sets Config.CustomInventory = "ox", which disables EnableDefaultInventory. esx_inventory then stops with ESX Default Inventory is disabled in config, please enable it to use this resource. If you use ox_inventory, do not ensure esx_inventory. See ox_inventory with ESX.

Installation

esx_inventory is part of the [core] folder. Start it after its dependencies:

server.cfg
ensure oxmysql
ensure esx_lib
ensure es_extended
ensure esx_inventory

The UI ships prebuilt in web/dist. You only need to rebuild it if you change web/src:

cd esx_inventory/web
pnpm install
pnpm build

Configuration

All keys live in config/main.lua.

KeyDefaultDescription
LocaleGetConvar("esx:locale", "en")Language used for UI strings and notifications.
PlayerSlots25Number of slots in the player grid. Slot indexes saved in KVP are clamped to this range.
HotbarSlots5Number of first slots styled as hotbar. One keybind is registered per hotbar slot.
ItemImageUrl"nui://esx_inventory/web/images/%s.png"Image URL template. %s is replaced with the item, account or weapon name.
NearbyPlayerDistance3.0Max distance (meters) for a player to appear in the "give" target list.
StorageBurst5Rate limiter capacity for storage put/take actions per player.
StorageRefillInterval1000Interval (ms) after which the storage rate limiter refills StorageBurst tokens.

Theme convars

The panel reads the shared ESX UI convars through xLib.colors.getESXTheme(). Set them with setr so the client can read them:

server.cfg
setr esx:ui:primaryColor "#fb9b04"
setr esx:ui:logoUrl "https://example.com/logo.png"

Supported convars: esx:ui:primaryColor, esx:ui:secondaryColor, esx:ui:backgroundColor, esx:ui:accentColor, esx:ui:logoUrl.

Item images

Put itemname.png files (for example bread.png, WEAPON_PISTOL.png, money.png) into web/images/. Missing images fall back to an icon. To use a CDN, change ItemImageUrl to something like "https://cdn.example.com/items/%s.png".

Keybinds

Keybinds are registered with xLib.addKeybind, so players can rebind them in Settings > Key Bindings > FiveM.

NameDefault keyAction
showinvF2Open or close the inventory.
hotbar1 ... hotbar51 ... 5Use the item in hotbar slot 1-5. Weapons are equipped or holstered.

While a hotbar key is held, the matching GTA weapon-wheel shortcut control is disabled so the key does not also switch weapons. The inventory does not open while the player is dead, the pause menu is active, or the player is not loaded yet.

Client exports

ShowInventory

exports.esx_inventory:ShowInventory()

Opens the inventory for the local player. Does nothing if it is already open, the NUI is not ready, the player is dead or the pause menu is open.

client/main.lua
RegisterCommand('inv', function()
    exports.esx_inventory:ShowInventory()
end, false)

Server exports (storage API)

A storage is a second panel next to the player inventory. The registering resource owns the data; esx_inventory handles the player side (remove before put, carry check before and after take, refund on failure) and runs one transaction at a time per player.

RegisterStorage

exports.esx_inventory:RegisterStorage(storageId, definition) -- returns boolean
NameTypeDescription
storageIdstringUnique, non-empty storage id.
definition.labelstringTitle shown on the panel. Required.
definition.canAccessfun(xPlayer): booleanAccess check. Required.
definition.getItemsfun(xPlayer): table[]Returns { name, count, label?, weight? } entries. Required.
definition.putItemfun(xPlayer, itemName, count): booleanAdds to your storage. Return true only after the state changed. Required.
definition.takeItemfun(xPlayer, itemName, count): booleanRemoves from your storage. Return true only after the state changed. Required.
definition.slotsnumber?Max distinct items. Default 30.
definition.maxWeightnumber?Max total weight, 0 disables the check. Default 0. Weights come from the es_extended item registry.
definition.coordsvector3?Optional location. Transfers are refused when the player is too far away.
definition.distancenumber?Allowed distance from coords. Default 5.0.

Returns false when the id is invalid, a callback is missing, or another resource already owns the id. The owning resource may call it again to replace its definition; open viewers are refreshed. Only item_standard entries are supported in storages: money and weapons cannot be stored through this API.

Callbacks must not yield

The callbacks run synchronously in your resource. Do not call MySQL.query.await, Wait or other yielding functions inside them. Keep the storage contents in memory and persist them separately.

server/storage.lua
local stock = {} -- [itemName] = count, load it from your database on start

local function toList()
    local list = {}
    for name, count in pairs(stock) do
        list[#list + 1] = { name = name, count = count }
    end
    return list
end

local function registerStorages()
    exports.esx_inventory:RegisterStorage('society_mechanic', {
        label = 'Mechanic Stock',
        slots = 30,
        maxWeight = 200,
        coords = vector3(-342.29, -133.37, 39.0),
        distance = 5.0,
        canAccess = function(xPlayer)
            return xPlayer.getJob().name == 'mechanic'
        end,
        getItems = function()
            return toList()
        end,
        putItem = function(_, itemName, count)
            stock[itemName] = (stock[itemName] or 0) + count
            return true
        end,
        takeItem = function(_, itemName, count)
            local current = stock[itemName] or 0
            if current < count then return false end
            stock[itemName] = current - count > 0 and current - count or nil
            return true
        end,
    })
end

AddEventHandler('esx_inventory:ready', registerStorages)

if GetResourceState('esx_inventory') == 'started' then
    registerStorages()
end

OpenStorage

exports.esx_inventory:OpenStorage(playerId, storageId) -- returns boolean
NameTypeDescription
playerIdnumberServer id of the player.
storageIdstringId passed to RegisterStorage.

Returns false if the storage does not exist, the player is not loaded, out of range, canAccess returns false, or getItems errors. The same function is also bound to the local server event esx_inventory:openStorage, so TriggerEvent('esx_inventory:openStorage', playerId, storageId) works from other server scripts.

server/main.lua
RegisterNetEvent('mechanic:openStock', function()
    local src = source
    if not exports.esx_inventory:OpenStorage(src, 'society_mechanic') then
        local xPlayer = ESX.GetPlayerFromId(src)
        if xPlayer then xPlayer.showNotification('You cannot open this storage.') end
    end
end)

RefreshStorage

exports.esx_inventory:RefreshStorage(storageId) -- returns boolean

Pushes the current getItems result to everyone viewing the storage. Call it after you change the contents outside of putItem / takeItem (for example from an admin command). Viewers who lost access or moved out of range get the panel closed.

UnregisterStorage

exports.esx_inventory:UnregisterStorage(storageId) -- returns boolean

Removes a storage and closes it for every viewer. Storages are also unregistered automatically when the owning resource stops.

Events

EventSideTypeDescription
esx_inventory:readyServerLocalFired when esx_inventory starts. Register your storages again here.
esx_inventory:openStorageServerLocal(playerId, storageId), same as OpenStorage.
esx_inventory:storagePutServerNet (internal)Sent by the UI. Rate limited and validated.
esx_inventory:storageTakeServerNet (internal)Sent by the UI. Rate limited and validated.
esx_inventory:storageClosedServerNet (internal)Sent when the player closes the storage panel.
esx_inventory:openStorage / refreshStorage / closeStorageClientNet (internal)Sent by the server to drive the storage panel.

Do not trigger the internal net events yourself; use the exports. The UI refreshes itself on the es_extended events esx:setInventory, esx:addInventoryItem, esx:removeInventoryItem, esx:addLoadoutItem, esx:removeLoadoutItem, esx:setAccountMoney and esx:playerLoaded, and closes on esx:onPlayerDeath.

Common issues

Frequently asked questions

Independent documentation

esx-fivem.com is a community project and is not affiliated with the ESX Framework team. Facts on this page were checked against the upstream source. Official resources: docs.esx-framework.org and github.com/esx-framework.
Edit on GitHub

Last updated on

On this page