FiveM Docs
Addons

esx_shops

esx_shops adds 24/7, liquor and LTD stores with a cart-based NUI to ESX Legacy. Covers setup, config keys, tax, shop zones, exports and pitfalls.

esx_shops is the official store resource for ESX Legacy. It places 24/7, Rob's Liquor and LTD Gasoline stores on the map and opens a Svelte-based shopping cart UI where players pick items, choose cash or bank and pay. Use it as your general store system or as a template for your own item shops.

Overview

Resourceesx_shops (version 2.0.0)
SideClient + server, NUI (web/dist)
Dependenciesesx_lib, es_extended, oxmysql
Optionalesx_addonaccount (tax collection), ox_inventory
SQL fileNone
Config filesshared/config/main.lua, shared/config/shops.lua

How a purchase works:

  1. The player walks into a marker and presses the interaction key. The client asks the server for the player's tax rate and opens the NUI.
  2. On checkout the client sends the cart through the esx_shops:purchaseItems callback.
  3. The server rate-limits the request, checks the zone, the distance to the shop, the payment method, every item name, quantity and price against Config.Zones, and recomputes the total. The client total must match the server total within Config.PriceTolerance.
  4. The server checks inventory weight, deducts money and adds the items. If adding an item fails, already added items are removed and the money is refunded.
  5. The tax part of the price is deposited into Config.TaxSocietyAccount when tax collection is on.

Installation

Add the resource

Place esx_shops in your resources folder. The repository ships a prebuilt UI in web/dist, so you only need Node.js if you change the Svelte source (cd web && npm install && npm run build).

Ensure it after its dependencies

server.cfg
ensure oxmysql
ensure esx_lib
ensure es_extended
ensure esx_addonaccount
ensure esx_shops

Prepare the tax account (optional)

With Config.EnableTaxCollection = true the tax goes into the shared addon account named in Config.TaxSocietyAccount (society_banker by default). Create it if it does not exist:

INSERT IGNORE INTO `addon_account` (`name`, `label`, `shared`) VALUES ('society_banker', 'Bank', 1);

With Config.Inventory = 'esx' the resource registers every configured shop item via ESX.AddItems on startup (label, weight or Config.DefaultItemWeight). You still want proper rows in your items table for items you use elsewhere.

Configuration

Main config (shared/config/main.lua)

KeyDefaultDescription
Config.DebugfalsePrints validation warnings and errors to the console.
Config.Inventory'esx''esx', 'ox_inventory' (or 'ox') or 'auto'. Falls back to ESX if ox_inventory is configured but not started.
Config.DefaultItemWeight1Weight used when a shop item has no weight.
Config.DefaultImagePath"nui://esx_shops/web/images"Base path for item images. Set to nil or '' to disable auto paths.
Config.DefaultImageFormat"png"File extension for auto-generated image paths.
Config.TaxRate0.19Tax share included in every price (19 %).
Config.EnableTaxCollectiontrueDeposit the tax into the society account. false only shows it in the UI.
Config.TaxSocietyAccount'society_banker'Shared addon account that receives tax.
Config.EnableTaxExemptionstrueLets jobs in TaxExemptJobs pay the net price.
Config.TaxExemptJobs{ 'police', 'ambulance' }Job names that do not pay tax.
Config.DrawDistance7.5Marker draw distance in meters.
Config.MarkerSize{x = 1.1, y = 0.7, z = 1.1}Marker scale.
Config.MarkerType29Marker type.
Config.MarkerColor{r = 50, g = 200, b = 50, a = 200}Marker color.
Config.MovementThreshold5.0Meters the player must move before nearby shops are recalculated.
Config.SleepNear / SleepMedium / SleepFar0 / 500 / 1500Marker thread sleep in ms (under 50 m, 50-100 m, further).
Config.PurchaseCooldownMs500Refill interval of the purchase rate limiter.
Config.CooldownExpiryMs10000Idle time after which limiter entries are cleaned up.
Config.PurchaseRateLimitCapacity3Token bucket size (burst of purchases allowed).
Config.PurchaseRateLimitRefill1Tokens added per interval.
Config.PurchaseRateLimitIntervalMsConfig.PurchaseCooldownMsInterval for token refill.
Config.MaxQuantityPerItem999Maximum quantity per cart line.
Config.MaxCartLines25Maximum distinct items per purchase.
Config.PriceTolerance0.001Allowed difference between client and server total.

Shop zones (shared/config/shops.lua)

Config.Zones ships with three zones: TwentyFourSeven, RobsLiquor and LTDgasoline. Each zone looks like this:

shared/config/shops.lua
Config.Zones.Hardware = {
    Items = {
        { name = 'repairkit', label = 'Repair Kit', price = 250, category = 'tools', weight = 2 },
        { name = 'bandage', label = 'Bandage', price = 40, category = 'medical' },
    },
    Categories = {
        { id = 'tools', label = 'Tools', icon = 'fa-solid fa-wrench' },
        { id = 'medical', label = 'Medical', icon = 'fa-solid fa-kit-medical' },
    },
    Pos = {
        vector3(2748.0, 3473.0, 55.7),
    },
    Size = 0.8,     -- blip scale
    Type = 59,      -- blip sprite
    Color = 25,     -- blip color
    ShowBlip = true,
    ShowMarker = true,
}
FieldTypeDescription
Items[].namestringItem name in your inventory.
Items[].labelstringName shown in the UI.
Items[].pricenumberGross price per unit (tax included). Must be greater than 0.
Items[].categorystring?Category id for filtering.
Items[].weightnumber?Weight used when the item is auto-registered for the ESX inventory.
Items[].imagestring?Custom image URL. Otherwise DefaultImagePath/<name>.<DefaultImageFormat>.
Items[].limitnumber?Passed to the UI as a per-transaction limit. The server only enforces Config.MaxQuantityPerItem.
Categoriestable?{ id, label, icon } entries, icons are Font Awesome classes.
Posvector3[]Every location of this shop.
Size, Type, ColornumberBlip scale, sprite and color.
ShowBlip, ShowMarkerbooleanToggle blip and 3D marker.

Tax math

Prices include tax. For a 100 item at a TaxRate of 0.19, a normal player pays 100 and 15.97 goes to the society account. A tax-exempt player pays 84.03 and no tax is collected.

The UI colors follow the ESX theme convars (esx:ui:*) via xLib.colors.getESXTheme.

Developer API

Server export IsItemInShop

exports.esx_shops:IsItemInShop(itemName, zone)
NameTypeDescription
itemNamestringItem name to look up.
zonestringKey in Config.Zones, e.g. 'TwentyFourSeven'.

Returns true when the item is sold in that zone, otherwise false.

server/main.lua
if exports.esx_shops:IsItemInShop('lockpick', 'TwentyFourSeven') then
    print('Lockpicks are sold in 24/7 stores')
end

Shared exports GetShopItems / GetShopCategories

Both are available on client and server and are marked as legacy. They return the Items or Categories table of a zone, or {} if the zone does not exist.

local items = exports.esx_shops:GetShopItems('RobsLiquor')

Client exports

ExportReturns
exports.esx_shops:IsShopOpen()true while the shop UI is open.
exports.esx_shops:GetCurrentShop()The zone name of the open shop, or nil.

Client event esx_shops:forceOpenShop

Opens the shop UI for a zone if it exists in Config.Zones.

server/main.lua
TriggerClientEvent('esx_shops:forceOpenShop', playerId, 'TwentyFourSeven')

Purchases still require the player to stand within 5 meters of one of that zone's Pos entries, so this is only useful near a real shop location (for example from a target or ped script).

Callbacks and network events

NameTypeNotes
esx_shops:purchaseItemsServer callbackUsed by the NUI. Arguments: purchaseData ({ items, total, paymentMethod }) and zone.
esx_shops:getTaxRateServer callbackReturns the player's tax rate and an optional exemption message.
esx_shops:legacyPurchaseServer net eventCompatibility event: (zone, items, paymentMethod). Answers with the client event esx_shops:legacyPurchaseResponse(success, message).
esx_shops:openShopNet eventLegacy placeholder. It only validates the argument and does nothing else.

Client-triggerable

esx_shops:purchaseItems and esx_shops:legacyPurchase can be called by any client. Both go through the same server pipeline (rate limit, distance check, server-side prices, inventory space, refund on failure), so clients cannot set prices. Keep Config.Zones the only source of prices and do not add your own purchase events that skip ProcessPurchase.

Common issues

  • "Not enough money" although the player has cash. The payment method chosen in the UI decides between money and bank.
  • Tax is not deposited. Config.TaxSocietyAccount does not exist in addon_account, or esx_addonaccount is not started. Enable Config.Debug to see the warning.
  • Item not added / inventory full. With the ESX inventory, every item must be registered and fit the player's max weight. With ox_inventory, CanCarryItem must succeed.
  • Images missing. Put a PNG named after the item into web/images or set image on the item.
  • Shop opens but purchases fail far from the marker. See the distance check above.

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