Commands#
Two of these are for players and the rest are for staff. Anything that writes to the database is re-checked on the server, so an ace alone is not enough — the caller still has to pass the admin check.
Command | Who | Description |
|---|---|---|
| Any player | Re-applies the player's saved appearance. Refused while they are dead, in a vehicle, falling, or inside the reload cooldown. |
| Any player | Deletes every prop stuck to their ped — the fix for a hat or bag left behind by another script. |
| Config.PedMenuGroup | Opens the full menu, free and with every category enabled. With a server id, opens it on that player instead. Registered only when Config.EnablePedMenu is true. |
| Admin | The creator: shops, catalogue photos, ped models, gloves, clothing packs and the catalogue editor. |
| Admin | Tunes the capture camera for a slot and exports a snippet for Config.ImageSaver.drawableList. Needs Config.ImageSaver.enable. |
| Console or admin | Prints every clothing pack it can see, its files, its slot count and what it would do with it. |
| Admin, in game | Opens the creator on the Clothing Packs tab. Refuses to run from the console. |
| Console or admin | Runs the fast replace scan and prints matched, unmatched, needs-verify and updated counts. With preview it changes nothing. |
| Console or admin | Prints the last hash scan: addon, replaced, unchanged, errors, and the distance spread. |
| Console or admin | Clears the replaced flag on every catalogue row at once. Not reversible. |
| Admin / console | One-shot migrations from another clothing script. See Integrations. |
There is also a family of vendor backfill commands for labels, game builds and AI metadata. They exist to build the shipped catalogue and are not part of normal operation.
How the Exports Are Named#
Almost every export is registered twice: once under qs-appearance and once under illenium-appearance, so a script written for illenium keeps working untouched. Write new code against the qs-appearance name.
exports['qs-appearance']:getPedAppearance(PlayerPedId()) -- use this
exports['illenium-appearance']:getPedAppearance(PlayerPedId()) -- compatibility aliasThe resource folder has to be named qs-appearance — it refuses to start otherwise — so these names are safe to hardcode.
Client Exports#
For reading what a player is wearing, dressing them, and opening the menu from your own script — a wardrobe prop, a heist disguise, a job uniform.
Reading an appearance
local ped = PlayerPedId()
-- everything at once: model, components, props, headBlend,
-- faceFeatures, headOverlays, hair, tattoos, eyeColor, pedScale
local appearance = exports['qs-appearance']:getPedAppearance(ped)
-- or one piece at a time
exports['qs-appearance']:getPedModel(ped)
exports['qs-appearance']:getPedComponents(ped) -- { component_id, drawable, texture }
exports['qs-appearance']:getPedProps(ped) -- { prop_id, drawable, texture }
exports['qs-appearance']:getPedHeadBlend(ped)
exports['qs-appearance']:getPedFaceFeatures(ped)
exports['qs-appearance']:getPedHeadOverlays(ped)
exports['qs-appearance']:getPedHair(ped)These read the live ped, so they work on any ped entity, not just the player's.
Dressing a player
-- the whole look, model included
exports['qs-appearance']:setPlayerAppearance(appearance)
-- a specific ped, without changing its model
exports['qs-appearance']:setPedAppearance(ped, appearance)
-- one slot: returns false when the game refused the drawable
local ok = exports['qs-appearance']:setPedComponent(ped, {
component_id = 1, drawable = 52, texture = 0
})
exports['qs-appearance']:setPedProp(ped, { prop_id = 0, drawable = 12, texture = 1 })
exports['qs-appearance']:setPedComponents(ped, components)
exports['qs-appearance']:setPedProps(ped, props)
exports['qs-appearance']:setPlayerModel('mp_f_freemode_01')
exports['qs-appearance']:setPedTattoos(ped, tattoos)setPedComponent and setPedProp apply the change and then read it back, so the boolean they return tells you whether the game actually accepted it. A refused drawable is restored to what it was rather than left broken.
Opening the menu
local cfg = exports['qs-appearance']:GetDefaultConfig()
cfg.character, cfg.creator, cfg.tattoos = true, true, true
exports['qs-appearance']:startPlayerCustomization(function(appearance)
if not appearance then
return print('the player cancelled')
end
TriggerServerEvent('qs-appearance:server:saveAppearance', appearance)
end, cfg)The callback receives the finished appearance, or nil when the player backed out. Passing nil as the config opens it with the defaults.
-- start from nothing and enable only what you want
local cfg = exports['qs-appearance']:GetDefaultConfig(true) -- everything off
cfg.componentConfig.hats = true
cfg.accessories = true
cfg = exports['qs-appearance']:NormalizeAccessoryComponentConfig(cfg)MergeShopConfig layers a stored shop config over the defaults, and NormalizeAccessoryComponentConfig translates illenium-style accessory names. An illenium config object is detected and converted on its own.
The ped list
CreateThread(function()
local peds = exports['qs-appearance']:GetPedList()
print(('%d peds available'):format(#peds))
end)Server Exports#
GetPedList
local peds = exports['qs-appearance']:GetPedList() -- string[]Every ped model name the server knows, hidden ones included.
The clothing pack scanner
The same scanner the Clothing Packs tab uses, exposed so you can script around it. The results come back as tables — no decoding needed.
local scan = exports['qs-appearance']:scanAllResources(json.encode({
ignoreResources = {}, ignoreNamePatterns = {}, excludeMapResources = true,
}))
print(('%d clothing packs found'):format(scan.resourceCount))
exports['qs-appearance']:scanResource('my_clothing_pack')
exports['qs-appearance']:getReplaceSlots(optionsJson)
exports['qs-appearance']:diagnoseScan('my_clothing_pack')
exports['qs-appearance']:packScannerInfo('') -- version and platformpackScannerInfo is the first thing to run when a scan finds nothing and you are opening a ticket.
Events You Can Trigger#
For opening a shop from a prop, a radial menu or a phone app, and for saving from your own flow.
-- server → one player
TriggerClientEvent('qs-appearance:client:openClothingShop', source, 3)
TriggerClientEvent('qs-appearance:client:openClothingShopMenu', source)
TriggerClientEvent('qs-appearance:client:reloadSkin', source)
TriggerClientEvent('clothing:notification', source, 'Outfit saved', 'success')
-- client, locally
TriggerEvent('qs-appearance:client:OpenBarberShop')
TriggerEvent('qs-appearance:client:OpenTattooShop')
TriggerEvent('qs-appearance:client:OpenSurgeonShop')
TriggerEvent('clothing:openOutfitMenu')
TriggerEvent('clothing:openJobOutfitMenu')-- client → server
TriggerServerEvent('qs-appearance:server:saveAppearance', appearance)
TriggerServerEvent('qs-appearance:server:saveOutfit', 'Work suit',
appearance.model, appearance.components, appearance.props)
TriggerServerEvent('qs-appearance:server:updateOutfit', id, model, components, props)
TriggerServerEvent('qs-appearance:server:deleteOutfit', id)
TriggerServerEvent('qs-appearance:server:resetOutfitCache') -- on a character switch
TriggerServerEvent('clothing:applyPlayerScale', 1.15) -- needs Config.EnableScaleopenClothingShop takes the id of a shop you placed, so the menu opens with that shop's categories and stock. openClothingShopMenu opens a generic menu not tied to any shop.
Hooks You Can Listen To#
The two that matter most for other resources.
-- the player closed the menu; nil means they cancelled
AddEventHandler('appearance:handleClose', function(appearance)
if not appearance then return end
print('finished as ' .. appearance.model)
end)
-- tattoos were just rebuilt on this ped
AddEventHandler('qs-appearance:client:tattoosApplied', function(ped)
-- re-add your own decorations here.
-- do NOT call ClearPedDecorations: it was already cleared for you.
AddPedDecorationFromHashes(ped, `mpbeach_overlays`, `fm_tats_m_000`)
end)Broadcasts you can also watch, each sent to everyone when the data behind it changes: clothing:setStores, clothing:createStore, clothing:updateStore, clothing:deleteStore, qs-appearance:syncStock, qs-appearance:syncPedModels, qs-appearance:syncFacePresets, clothing:syncGloves and clothing:syncTopHandMappings.
Player height is replicated as a state bag, so any script can read it without a callback:
AddStateBagChangeHandler('pedScale', nil, function(bagName, _, value)
print(bagName, 'scale is now', value or 1.0)
end)Callbacks#
ox_lib callbacks, all registered on the server. Call them from a client.
Name | Returns |
|---|---|
| The caller's saved appearance, or nil. |
| Whether they can afford a shop type, and its price. |
| Takes a price from money, bank or black_money. True on success. |
| Whether the caller passes the admin check. |
| The caller's outfits, and writing to them. |
| The uniforms for the caller's job and grade. |
| Players within three metres, for outfit sharing. |
| Every job with its label and grades. |
| What one shop stocks for one ped model. |
| A page of the catalogue, with the same filters the shop menu uses. |
CreateThread(function()
local appearance = lib.callback.await('qs-appearance:server:getAppearance', false)
if appearance then
exports['qs-appearance']:setPlayerAppearance(appearance)
end
local canAfford, price = lib.callback.await(
'qs-appearance:server:hasMoney', false, 'barber', 'mp_m_freemode_01')
if not canAfford then print(('needs $%d'):format(price)) end
end)Every creator and catalogue callback — placing shops, saving stock, editing or deleting rows, ped models, gloves, scans — refuses a caller that does not pass the admin check, and saving a job outfit drops a player who forges it. Build your own tools on the public surface above.



