esx_shops
A modern, performance-optimized shop system for ESX Legacy with a sleek NUI interface.
Key Features
- Modern Svelte 5 UI with smooth animations
- Dynamic tax system with job-based exemptions
- Performance-optimized client
- Server-side validation prevents all common exploits
- Supports both ESX and ox_inventory
- Fully configurable categories and items
- Theme integration via ESX convars
Requirements
- es_extended
- esx_addonaccount (only if using tax collection)
Installation
- Download and place
esx_shopsin your resources folder - Build the UI:
cd esx_shops/web
npm install
npm run build- Add to server.cfg:
ensure esx_shops
- Configure in
shared/config/(see below)
File Structure
esx_shops/
├── fxmanifest.lua # Resource manifest
├── README.md # This file
├── types.lua # Lua type definitions
│
├── shared/
│ ├── locale.lua # Self-contained locale system
│ ├── config/
│ │ ├── main.lua # Core configuration (tax, inventory, markers, security)
│ │ └── shops.lua # Shop definitions (zones, items, categories)
│ ├── functions.lua # Shared utilities (ProcessItemImages, DebugPrint, etc.)
│ └── compat.lua # Backward compatibility exports
│
├── client/
│ ├── main.lua # Entry point
│ ├── functions.lua # Client utilities (GetESXThemeColors)
│ ├── compat.lua # Client backward compatibility
│ └── modules/
│ ├── blips.lua # Blip creation & management
│ ├── markers.lua # Marker drawing & proximity cache
│ ├── nui.lua # NUI open/close/purchase callbacks
│ └── interactions.lua # ESX interaction registration
│
├── server/
│ ├── main.lua # Entry point (callback registration)
│ ├── functions.lua # Server utilities (ValidatePlayer, CheckMoney, etc.)
│ ├── compat.lua # Server backward compatibility
│ └── modules/
│ ├── tax.lua # Tax calculation & collection
│ ├── inventory.lua # Inventory validation (ESX/ox_inventory)
│ ├── validation.lua # Rate limiting, price validation, item checks
│ └── transactions.lua # Purchase processing orchestration
│
└── web/
├── src/
│ ├── components/ # Svelte components
│ ├── stores/ # State management
│ ├── utils/ # NUI helpers
│ └── App.svelte # Root component
└── dist/ # Built files (created by npm run build)
Configuration
Main Config (shared/config/main.lua)
---@type table
Config = Config or {}
-- Enable debug prints in console (set to true for troubleshooting)
Config.Debug = false
-- ════════════════════════════════════════════════════════════════
-- CORE CONFIGURATION
-- ════════════════════════════════════════════════════════════════
-- Inventory system ('esx' or 'ox_inventory')
Config.Inventory = 'ox_inventory'
-- ════════════════════════════════════════════════════════════════
-- IMAGE CONFIGURATION
-- ════════════════════════════════════════════════════════════════
-- Automatic image path generation for items
-- If an item doesn't have an 'image' field, it will auto-generate as:
-- {DefaultImagePath}/{itemName}.{DefaultImageFormat}
-- Set to nil or empty string to disable auto-generation
Config.DefaultImagePath = "nui://esx_shops/web/images"
Config.DefaultImageFormat = "png" -- png, webp, jpg, etc.
-- ════════════════════════════════════════════════════════════════
-- TAX SYSTEM CONFIGURATION
-- ════════════════════════════════════════════════════════════════
-- Default tax rate (19% VAT)
Config.TaxRate = 0.19
-- Enable/Disable tax collection to society account
-- false = Tax is only displayed, not collected
-- true = Tax is collected and deposited to society account
Config.EnableTaxCollection = true
-- Society account for tax collection (requires esx_addonaccount)
-- Only used if Config.EnableTaxCollection = true
-- Make sure this society exists in your database
Config.TaxSocietyAccount = 'society_banker'
-- Enable/Disable job-based tax exemptions
-- false = Everyone pays full tax
-- true = Jobs in TaxExemptJobs list pay 0% tax
Config.EnableTaxExemptions = true
-- Jobs that are exempt from paying tax
-- Only used if Config.EnableTaxExemptions = true
-- These jobs see 0% tax rate with special message in UI
Config.TaxExemptJobs = {
'police', -- Law enforcement
'ambulance', -- Emergency medical services
}
-- ════════════════════════════════════════════════════════════════
-- MARKER CONFIGURATION
-- ════════════════════════════════════════════════════════════════
Config.DrawDistance = 7.5
Config.MarkerSize = {x = 1.1, y = 0.7, z = 1.1}
Config.MarkerType = 29
Config.MarkerColor = {r = 50, g = 200, b = 50, a = 200}
-- ════════════════════════════════════════════════════════════════
-- PERFORMANCE CONFIGURATION
-- ════════════════════════════════════════════════════════════════
-- Movement threshold for recalculating nearby shops (meters)
Config.MovementThreshold = 5.0
-- Sleep times for marker thread (ms)
Config.SleepNear = 0 -- Within 50m
Config.SleepMedium = 500 -- 50-100m away
Config.SleepFar = 1500 -- Far away
-- ════════════════════════════════════════════════════════════════
-- SECURITY CONFIGURATION
-- ════════════════════════════════════════════════════════════════
-- Rate limiting: cooldown between purchases (ms)
Config.PurchaseCooldownMs = 500
-- Auto-expire rate limit entries (ms)
Config.CooldownExpiryMs = 10000
-- Maximum quantity per item per transaction
Config.MaxQuantityPerItem = 999
-- Price validation tolerance
Config.PriceTolerance = 0.001Shop Config (shared/config/shops.lua)
---@type table<string, ShopZone>
Config.Zones = Config.Zones or {}
-- ════════════════════════════════════════════════════════════════
-- SHOP DEFINITIONS
-- ════════════════════════════════════════════════════════════════
-- Categories with FontAwesome icons (Find more at: https://fontawesome.com/icons)
-- Icon format: "fa-solid fa-icon-name" or "fa-regular fa-icon-name"
Config.Zones.TwentyFourSeven = {
Items = {
{name = "burger", label = "Burger", price = 15, category = "food"},
{name = "water", label = "Water", price = 10, category = "drinks"},
...
},
Categories = {
{id = "food", label = "Food", icon = "fa-solid fa-burger"},
{id = "drinks", label = "Drinks", icon = "fa-solid fa-bottle-water"},
...
},
Pos = {
vector3(373.8, 325.8, 103.5),
vector3(2557.4, 382.2, 108.6),
vector3(-3038.9, 585.9, 7.9),
...
},
Size = 0.8,
Type = 59,
Color = 25,
ShowBlip = true,
ShowMarker = true
}
--ADD MORE SHOPSZone Format Reference
| Field | Type | Description |
|---|---|---|
Items | table | Array of items for sale |
Items[].name | string | Item identifier (must match your inventory) |
Items[].label | string | Display name shown in UI |
Items[].price | number | Price per unit |
Items[].category | string | Category ID for grouping (optional) |
Items[].image | string | Custom image URL/path (optional, overrides auto-generation) |
Categories | table | Array of category definitions |
Categories[].id | string | Category identifier (must match item category) |
Categories[].label | string | Display name shown in UI |
Categories[].icon | string | FontAwesome 6 icon class (e.g. fa-solid fa-burger) |
Pos | table | Array of vector3 locations for this shop |
Size | number | Blip size on map |
Type | number | Blip type ID (see FiveM docs) |
Color | number | Blip color ID |
ShowBlip | boolean | Show blip on minimap/map |
ShowMarker | boolean | Show 3D world marker |
Tax System Explained
Tax Collection (Config.EnableTaxCollection = true):
- Requires
esx_addonaccountresource - Tax deposits to
Config.TaxSocietyAccount(society_bankerby default) - Logs warning if society account doesn’t exist
Tax Exemptions (Config.EnableTaxExemptions = true):
- Jobs in
Config.TaxExemptJobspay 0% tax - Exempt players see ”⭐ Thanks for your service!” message in UI
- Tax is calculated from gross prices (price includes tax)
Example:
Item: $100 (gross price with 19% tax)
Net: $84.03
Tax: $15.97
---
Police (exempt): Pays $84.03
Civilian: Pays $100.00
Development
cd web
# Development (auto-rebuild)
npm run dev:game
# Production build
npm run build
# Type checking
npm run check:strict