SandCastle Clothing Inventory
A standalone EUP clothing inventory, outfit system, and admin shop for FiveM. Runs on its own, without needing any framework or inventory resource.
Standalone means standalone. This resource works entirely on its own out of the box: no framework, no other inventory system needed to install and use it.
Making it talk to your framework's economy (ESX, QBCore, etc.) or a specific inventory is optional, and it happens by editing config.lua yourself (see the Money bridge section below). That means writing a bit of Lua. It's not a one-click toggle, and it's not something a first-time user with zero coding experience can safely do. If that's not you, either run it standalone as shipped, or get a developer to wire the bridge up for you.
config.lua, none of it hardwired.Requirements
| Resource | Needed for |
|---|---|
oxmysql | Required. All inventory, outfit, and shop data. |
ox_inventory | Optional, only needed if you enable the ox_inventory example in the money bridge (see below). |
ox_lib | Optional, only needed if you enable the ox_lib example in the notification bridge. |
No dependency line is declared in fxmanifest.lua for either. The script starts fine without them; those two integrations only run once you wire them up yourself.
Installation
Import sql/install.sql into your database once. It creates all 15 tables the script needs (inventory, outfits, shops, categories, scene config, etc.). Safe to re-run: every table uses CREATE TABLE IF NOT EXISTS.
Drop the resource folder into resources/. Add it to server.cfg, making sure oxmysql starts first:
ensure oxmysql
ensure SandCastle_ClothingInventory
exec @SandCastle_ClothingInventory/permissions.cfg
Edit permissions.cfg and add your own admin license(s):
add_principal identifier.license:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX group.eup_admin
Find your license by connecting to the server and checking the connection logs, or server-side with print(GetPlayerIdentifierByType(source, 'license')).
Open config.lua and discord_config.lua and set them up for your server. Details in the Configuration section below.
In-game as an admin, run /eupadmin to create categories, shops, and item listings, or open the Outfit Creator to build reusable outfit templates.
Which files can I edit?
Only two files: everything you can configure lives in one of these two, and they're the only ones you should ever need to open.
config.luadiscord_config.lua
Every other .lua file (client, server, shared) is protected, so don't try to edit them. The web/ folder (HTML/CSS/JS behind the NUI) isn't locked and you're free to look through it, but there's nothing in there you need to change.
Adding your own images
The resource ships with exactly one image: web/images/default_clothing.png. It's the fallback shown for any piece that doesn't have its own picture. EUP packs vary too much from server to server to ship images for every possible drawable, so building your own set is on you.
mp_m_freemode_01 and mp_f_freemode_01, so an image shot on one model doesn't apply to the other. To catalog female pieces in the Shop Creator or Outfit Creator, use the "Preview as Male / Female" toggle above the EUP Library panel, it spawns a separate preview ped of that gender so you can scan and photograph its drawables without touching your own character.Take clean screenshots of each drawable in-game with fivem-greenscreener (external tool, not included with this resource). It poses the ped and isolates each piece against a clean background for you. Run it once per gender if you want images for both.
Name each PNG to match what the script looks for:
male_{slotId}_{drawable}.png (male components, e.g. male_11_73.png)
male_prop_{slotId}_{drawable}.png (male props, e.g. male_prop_0_228.png)
female_{slotId}_{drawable}.png (female components, e.g. female_11_58.png)
female_prop_{slotId}_{drawable}.png (female props, e.g. female_prop_0_112.png)
Slot IDs are the native GTA component/prop indexes. You don't need to look them up: open the "All slots" dropdown in the Shop Creator or Outfit Creator in-game and every entry already shows its ID, like Jacket (C11) or Hat / Helmet (P0). C is for component, P is for prop.
Convert them to WebP with the converter script already included at web/png_to_webp.py. It needs Python 3 and Pillow:
pip install pillow
python3 web/png_to_webp.py path/to/your/screenshots web/images
Run it with no arguments and it reads from and writes to the current folder instead.
Drop the resulting .webp files into web/images/ and restart the resource. Each piece picks up its picture automatically wherever it shows up, inventory, shop catalog, outfit creator, all of it.
Male & female peds
The script tells male and female apart automatically, on both the client and the server, by checking the player's ped model (mp_m_freemode_01 vs mp_f_freemode_01). There's nothing to configure to turn this on, it's always active. The one thing you do need to set up yourself is Config.DefaultDrawables, which has separate male and female tables.
What's kept separate per gender
- The shop catalog. Every cataloged item (
Config.DefaultDrawablesaside) is tagged male or female when you save it in the Shop Creator. A shop's viewer only ever shows the buyer their own gender's items, an empty category still shows up if it only has the other gender's items in it, it just looks empty to that buyer. - Outfit templates (admin-created, given out via the Outfit Creator or the
GiveOutfitexport/event). Tagged with whatever gender the template was built on. - Each player's own inventory and outfits. Every piece a player owns and every outfit they've saved is tagged with the gender it was acquired under. Opening the inventory (F3) only ever shows the player their current gender's stuff.
- The player's active/equipped outfit. If a player has an outfit equipped and their ped's gender changes, it gets unequipped automatically rather than staying on and showing broken drawables.
When the check happens
Every time, not just once. The gender check re-runs on every single inventory open, on top of the usual spawn/resource-start refresh. If something external to this script changes a player's ped model mid-session, the very next time they press F3 they'll see their own-gender inventory and outfits, and any now-mismatched active outfit gets cleared automatically. No reconnect needed.
Cataloging female items as an admin
The Shop Creator and Outfit Creator both have a "Preview as Male / Female" toggle above the EUP Library panel. Switching it doesn't touch your own character, it spawns a separate preview ped of the chosen gender so you can scan its drawables, screenshot them, and save items or build outfit templates for it, all without leaving your own body. See Adding your own images for the male_/female_ naming convention that goes with it.
General settings
Everything lives in config.lua at the resource root.
Admin access
Config.AdminAce = 'eup.admin'
The ACE permission checked server-side for every admin action. Matches the group.eup_admin group set up in permissions.cfg; rename them together if you change one.
Chat commands
| Setting | Default | Effect |
|---|---|---|
Config.AdminCommandEnabled | true | Toggles the /eupadmin command. The panel stays reachable via export/event either way (see Exports & events). |
Config.AdminCommandName | 'eupadmin' | Rename the command if it conflicts with another script. |
Config.GiveCurrentEupCommandEnabled | true | Toggles /givecurrenteup. |
Config.GiveCurrentEupCommandName | 'givecurrenteup' | Rename that command too. |
Default drawables
Used by /givecurrenteup, the Outfit Creator's "Fill from ped" button, and the shop's "Strip" button to know which drawable on each slot means "nothing", so they don't treat your base ped model's default clothes as EUP items. Set separately per gender, since the same drawable number is a different mesh on mp_m_freemode_01 and mp_f_freemode_01.
Config.DefaultDrawables = {
male = {
component = {
[1] = 0, -- Mask
[3] = 15, -- Torso / Arms
[4] = 14, -- Legs
...
},
},
female = {
component = {
[1] = 0, -- Mask
[3] = 15, -- Torso / Arms
[4] = 14, -- Legs
...
},
},
}
Animations
Config.Animations, Config.PropAnimations, and Config.OutfitAnimation control the dressing animation played per slot when equipping/unequipping, and the generic one played for a full outfit swap. Each entry is an animation dict/clip pair with a flag and duration.
Money bridge
The script has no dependency on any economy framework. Out of the box, every shop purchase just goes through for free, so you can test the shop right away without setting anything up.
Config.HasMoney = function(src, amount)
return true
end
Config.RemoveMoney = function(src, amount)
return true
end
config.lua. Uncomment the one you need:-- ox_inventory example:
Config.HasMoney = function(src, amount)
return exports.ox_inventory:GetItemCount(src, 'money') >= amount
end
Config.RemoveMoney = function(src, amount)
return exports.ox_inventory:RemoveItem(src, 'money', amount)
end
-- ESX example:
Config.HasMoney = function(src, amount)
local xPlayer = exports.es_extended:GetPlayerFromId(src)
return xPlayer and xPlayer.getMoney() >= amount
end
Config.RemoveMoney = function(src, amount)
local xPlayer = exports.es_extended:GetPlayerFromId(src)
if not xPlayer or xPlayer.getMoney() < amount then return false end
xPlayer.removeMoney(amount)
return true
end
Both functions are also exposed as exports (HasMoney / RemoveMoney) so any other resource can reuse the exact same bridge instead of duplicating the check.
Notification bridge
Default: native GTA notification, zero dependency.
Config.Notify = function(ntype, title, message, duration)
SetNotificationTextEntry('STRING')
AddTextComponentString(('%s: %s'):format(title or '', message or ''))
DrawNotification(false, true)
end
ox_lib example, also already in config.lua as a comment:
Config.Notify = function(ntype, title, message, duration)
exports.ox_lib:notify({ type = ntype, title = title, description = message, duration = duration })
end
Also exposed as the Notify client export.
Preview scene
Config.PreviewScene is the fixed spot the player is teleported to (invisible) and the ped clone spawns at, for shop/creator previews. The default coordinates shipped with the resource are just a placeholder.
/coords to find the values. Or, easier, just use the admin panel's Scene tab in-game: it lets you capture player/ped spawn positions and adjust the camera live with sliders, then save.Config.PreviewScene = {
playerSpawn = vector4(x, y, z, heading),
pedSpawn = vector4(x, y, z, heading),
camPos = vector3(x, y, z),
camLookAt = vector3(x, y, z),
camFov = 47.0,
}
Discord role gating (optional)
Shop categories can optionally be restricted to players holding a specific Discord role. This lives in its own file, discord_config.lua, deliberately separate from config.lua:
Config.DiscordGuild = ''
Config.DiscordToken = ''
discord_config.lua is loaded as a server_script on purpose. A shared_script gets downloaded to every connecting client in the clear, which would leak your bot token to players. Leave both fields empty to disable role-gated categories entirely; they'll simply stay inaccessible.This is completely separate from admin access. Config.AdminAce (see above) is the only thing gating the admin panel, and it doesn't need Discord at all.
Commands
| Command | Access | Description |
|---|---|---|
F3 keybind | Everyone | Open/close the clothing inventory. Rebindable per-player in FiveM's own key bindings settings. |
/eupadmin | Admin | Open the admin panel: Zones, Outfits (templates), and Scene tabs. |
/givecurrenteup | Admin | Scans your ped's currently worn outfit and adds each piece as an EUP item in your inventory. |
/wype [license:xxxx] | Admin | Wipes a player's EUP data only (inventory, outfits, equipped slots, active outfit). Doesn't touch their character or any other framework data. Destructive and asks for no confirmation, so use it carefully. |
/eupbulkshop confirm | Admin | Bulk-generates a starter shop: scans every EUP drawable not already in your catalog (male and female) and adds it, auto-named, into a shop called "Auto Shop" with one category per slot type. Never touches anything you've already configured, safe to run again after adding a new EUP pack. Running it without confirm just prints the warning, it won't do anything. |
Player guide
- Press F3 to open the inventory.
- Right-click an item for the context menu: Equip / Give / Inspect / Delete.
- CTRL + right-click an item to equip it directly.
- SHIFT + right-click an item to add it to the currently active outfit.
- Drag & drop items between the inventory, character slots, and outfit slots.
- Create, rename, or delete outfits from the outfit panel. Deleting requires two clicks (button turns into a ⚠ warning first, confirms on the second click within 3 seconds).
- Give an entire outfit, or a single item, to any player within 10 meters.
Admin guide
Shop Creator
Opened via the admin panel's Shops action. Pick a slot to scan every drawable/texture available on your ped model, name the ones you want sellable, group them into categories (optionally restricted to a Discord role), assign categories to a shop, and set per-shop price overrides.
Here's what a player sees when they walk into a shop: browse drawables/textures on the left, review the cart and pay on the right.
Bulk shop generator
Don't want to name hundreds of drawables by hand? Run /eupbulkshop confirm and it scans your whole EUP pack, male and female, and dumps everything not already in your catalog into a shop called "Auto Shop", sorted into one category per slot type ("Auto: Jacket", "Auto: Hat / Helmet", etc.). Items are named after their slot and drawable number ("Jacket 15"), textures are named "Texture 0", "Texture 1", and so on, and everything is priced at 0 by default.
For transparency, here are the real numbers from testing on a full EUP pack: ~5,000 items and ~41,000 textures created in one run. The lag only happens during that generation itself, once it's done, opening the resulting shop is back to a brief, unnoticeable hitch (a few milliseconds).
Build a template outfit piece by piece, save it, and either hand it to yourself from the admin panel's Outfits tab (creates and equips it instantly, no item pickup step) or let another resource call the GiveOutfit export/event.
Zones tab
Walk to where you want a shop accessible, then click Set here next to that shop in the admin panel to place its interaction zone and radius.
Scene tab
Adjust and save the shared preview camera/ped spawn used by every shop and creator preview. See Preview scene above for more. You can also save multiple named camera presets and switch between them.
Exports & events
The integration surface for other resources. Internal eup:... events used between this resource's own client and server are not listed. They're implementation detail, not meant to be called externally.
How to call an export
From another resource's Lua file, on the same side (client or server) the export is listed under:
exports['SandCastle_ClothingInventory']:FunctionName(arg1, arg2)
Swap FunctionName for any export name from the tables below.
How to use an event
To trigger one:
TriggerEvent('EventName', arg1, arg2) -- locally, same side
TriggerServerEvent('EventName', arg1, arg2) -- client → server
TriggerClientEvent('EventName', targetSrc, arg1, arg2) -- server → client
To listen for one:
RegisterNetEvent('EventName', function(arg1, arg2)
-- your code here
end)
Server-side
| Type | Name | Usage |
|---|---|---|
| Export | GiveOutfit(targetSrc, templateId, outfitId) | Creates and equips an outfit directly on a player. Pass templateId for an admin template, or outfitId to restore one they already own. |
| Event | SandCastle_ClothingInventory:giveOutfit | Same, as an event: TriggerEvent('SandCastle_ClothingInventory:giveOutfit', targetSrc, templateId) |
| Export | HasMoney(src, amount) | Delegates to Config.HasMoney. |
| Export | RemoveMoney(src, amount) | Delegates to Config.RemoveMoney. |
Client-side
| Type | Name | Usage |
|---|---|---|
| Export | GetPlayerInventory() | Returns the local player's current inventory. |
| Export | GetPlayerOutfits() | Returns their saved outfits. |
| Export | EquipSlot(slotType, slotId, drawable, texture) | Equip a single piece. |
| Export | UnequipSlot(slotType, slotId) | Remove a piece from a slot. |
| Export | EquipOutfit(outfitId) | Equip a full outfit. |
| Export | ReloadSkin() | Immediately re-applies the equipped outfit/slots. |
| Export | Notify(ntype, title, message, duration) | Delegates to Config.Notify. |
| Export | EupAdminOpen() | Opens the admin panel. |
| Event | SandCastle_ClothingInventory:reloadSkin | Re-applies the skin after a 300ms delay (built for revive hooks). TriggerEvent(...) locally, or TriggerClientEvent(..., targetSrc) from the server. |
| Event | SandCastle_ClothingInventory:openAdmin | Opens the admin panel. Event form of EupAdminOpen. |