esx_accessories
Set up esx_accessories for ESX Legacy. Covers mask, helmet, glasses and ear shops, the datastore SQL, every config key, server events, callbacks and the J keybind.
esx_accessories adds shop markers where players buy masks, helmets, glasses and ear accessories, and a personal menu (default key J) to put them on and take them off again. Purchases are stored per character in esx_datastore, so the accessory survives relogs.
Overview
| Resource | esx_accessories |
| Side | Client + server |
| Dependencies | esx_lib, es_extended, esx_skin, esx_datastore, oxmysql (loaded via @oxmysql/lib/MySQL.lua) |
| Database | Four rows in the datastore table |
| UI | ESX.OpenContext menus, esx_skin restricted skin menu |
In-game flow:
- The player walks into a shop marker (
Config.Zones) and pressesE. esx_skinopens a restricted menu that only shows the<type>_1and<type>_2sliders of that accessory.- On confirmation the server charges
Config.Pricefrom cash and stores the chosen drawable and texture. - Later the player presses
J, picks Helmet, Ears, Mask or Glasses and toggles the saved item on or off.
Installation
Import the SQL
Run esx_accessories.sql against your ESX database. It inserts the four datastores the script reads and writes:
INSERT INTO `datastore` (name, label, shared) VALUES
('user_ears', 'Ears', 0),
('user_glasses', 'Glasses', 0),
('user_helmet', 'Helmet', 0),
('user_mask', 'Mask', 0)
;Translated labels are in the localization/ folder (de, fr, it, nl, sl, sr, sv, tr). Import only one of them.
Ensure the resource
Start it after its dependencies:
ensure oxmysql
ensure es_extended
ensure esx_lib
ensure skinchanger
ensure esx_skin
ensure esx_datastore
ensure esx_accessoriesConfiguration
All keys live in config.lua.
| Key | Default | Description |
|---|---|---|
Config.Locale | GetConvar('esx:locale', 'en') | Locale used for menus and notifications. |
Config.Price | 100 | Cash price of one accessory. Charged with xPlayer.removeMoney. |
Config.ShopDistance | 3.0 | Server-side maximum distance (meters) to a shop position for buying and saving. |
Config.PurchaseSessionDuration | 60000 | Milliseconds after payment during which the server accepts the save event. |
Config.EnableControls | true | Registers the accessory key mapping (default J) that opens the accessory menu. |
Config.DrawDistance | 10 | Distance at which shop markers are drawn. |
Config.Size | {x = 1.5, y = 1.5, z = 1.0} | Marker size. Size.x is also the interaction radius. |
Config.Color | {r = 50, g = 200, b = 50} | Marker color (alpha is fixed to 255). |
Config.Type | 1 | Marker type. Set to -1 to create no markers at all. |
Config.ShopsBlips | Only Mask at vector3(-1338.1, -1278.2, 3.8) | Map blips per accessory type: Pos (list of vector3) and Blip = {sprite, color}. |
Config.Zones | Ears, Mask, Helmet, Glasses | Shop positions per accessory type, each { Pos = { vector3, ... } }. |
Zone keys are fixed
The keys of Config.Zones must be Ears, Mask, Helmet or Glasses. The server normalizes the accessory name against that list and rejects anything else, so a custom key like Hats will never be purchasable.
Setting Config.Type = -1 skips the whole marker thread. Because the markers also handle enter and exit detection, the shops then stop working. Use a very small Config.Size or a transparent color if you only want to hide the visuals.
The default Config.ShopsBlips only adds a blip for the mask shop because the other zones sit inside clothing stores that esx_clotheshop already marks on the map.
Server events
esx_accessories:pay
TriggerServerEvent('esx_accessories:pay', accessory)| Name | Type | Description |
|---|---|---|
accessory | string | 'Ears', 'Mask', 'Helmet' or 'Glasses' (case-insensitive). |
Charges Config.Price and opens a purchase session for that accessory. The server checks that the player exists, stands near a matching zone and has enough cash. The built-in client uses the esx_accessories:buy callback instead, which runs the same logic and returns the result.
Client-triggerable
Any client can fire this event. It is safe because the server validates position and balance, but it only removes money; nothing is granted until esx_accessories:save follows.
esx_accessories:save
TriggerServerEvent('esx_accessories:save', skin, accessory)| Name | Type | Description |
|---|---|---|
skin | table | Skin table from skinchanger:getSkin. Only <type>_1 and <type>_2 are stored. |
accessory | string | Accessory type, same values as above. |
Writes has<Type> = true and skin = { <type>_1, <type>_2 } into the user_<type> datastore. The server rejects the call unless there is an unexpired paid session for that accessory and the player is still near the shop. The session is consumed on success.
Callbacks
Callbacks are registered with xLib.callback.registerCompat, so both xLib.callback and ESX.TriggerServerCallback can call them.
| Name | Arguments | Returns |
|---|---|---|
esx_accessories:get | accessory | hasAccessory (boolean), skin (table with <type>_1 and <type>_2) |
esx_accessories:buy | accessory | true if payment succeeded and a session was opened |
esx_accessories:checkMoney | accessory | true if the player is near the shop and has at least Config.Price cash |
Example: check whether the local player owns a helmet before offering it in your own menu.
xLib.callback('esx_accessories:get', false, function(hasHelmet, helmetSkin)
if not hasHelmet then
return ESX.ShowNotification('You do not own a helmet yet.')
end
TriggerEvent('skinchanger:getSkin', function(skin)
TriggerEvent('skinchanger:loadClothes', skin, helmetSkin)
end)
end, 'Helmet')Client events
These are local events (not network events) fired by the marker zones. You can listen to them, for example to show your own help text.
| Event | Argument | Fired when |
|---|---|---|
esx_accessories:hasEnteredMarker | zone (string, e.g. 'Mask') | The player enters a shop marker. |
esx_accessories:hasExitedMarker | zone | The player leaves a shop marker. The open context menu is closed. |
Reading the stored data from another resource
The data is a normal private datastore, so server scripts can read it through esx_datastore:
local xPlayer = ESX.GetPlayerFromId(source)
TriggerEvent('esx_datastore:getDataStore', 'user_mask', xPlayer.getIdentifier(), function(store)
local owned = store.get('hasMask') or false
local skin = store.get('skin') or {}
print(('Owns mask: %s, drawable %s'):format(owned, skin.mask_1))
end)Common issues
- "No mask/helmet" notification although the item was bought. The datastore rows from the SQL file are missing, or esx_datastore started after the purchase. Check that
user_maskand the others exist in thedatastoretable. - Purchase charged but nothing saved. The player walked out of
Config.ShopDistanceor took longer thanConfig.PurchaseSessionDurationin the confirmation menu. The money is not refunded in that case. - Shop does nothing when pressing E. You changed
Config.Typeto-1or renamed a zone key. See the configuration notes above. - Masks taken off show a blank head. Mask "off" sets drawable
0; props (helmet, glasses, ears) use-1. Custom clothing packs may need other values.
Related
Frequently asked questions
Independent documentation
Last updated on
ESX Legacy Addons
Overview of all 39 official ESX Legacy addons with categories, dependencies, installation order and the 2026 security hardening SQL migration.
esx_accessorymanager
esx_accessorymanager adds a radial wardrobe to ESX Legacy for taking off and restoring clothing and props. Config keys, client exports, F11 keybind and setup.