How Integrations Work#
Clothing Creator touches very little outside itself. Everything framework-specific sits in a small, unencrypted bridge under custom/, and published clothing reaches the rest of your server the way base-game clothing does — as drawables on the freemode ped.
Layer | Supported | Without it |
|---|---|---|
Framework | es_extended · qb-core · qbx_core | Standalone: admins are whoever has the |
Appearance menu | Any. Restoring the outfit after a refresh: qs-appearance · illenium-appearance · fivem-appearance · esx_skin | Published pieces are still taken off and put back on around every publish by Clothing Creator itself. |
Database | oxmysql | Required. |
Library | ox_lib | Required. |
Notifications | The studio's own toasts, or any notify resource | The built-in toasts are used. |
Logs | Discord webhooks | Nothing is sent. The publish history is still kept in the database. |
Appearance Menus#
There is nothing to wire up. Appearance menus — illenium-appearance, qs-appearance, fivem-appearance, esx_skin, qb-clothing, rcore_clothing and the rest — list clothing by counting the drawables a freemode ped has in each slot, and a publish raises that count. A new item simply appears as the next number in its slot, for every player, in whatever menu they already use. Outfits saved by those menus keep working, because a retired item leaves an invisible placeholder behind instead of shifting the numbers after it.
After the clothing refresh of a publish, a player's skin is reloaded through the first of these that is running:
TriggerEvent('qs-appearance:client:reloadSkin', true) -- qs-appearance
TriggerEvent('illenium-appearance:client:reloadSkin') -- illenium-appearance
TriggerEvent('fivem-appearance:reloadSkin') -- fivem-appearance
TriggerEvent('skinchanger:getSkin', ...loadSkin) -- esx_skinItem names in your menu#
Menus show drawables as numbers. If yours can display labels, Clothing Creator writes the name of every live item into qs-clothing-assets/integrations/labels.json (and a labels.lua returning the same table) on every start and after every publish or retire:
local labels = json.decode(LoadResourceFile('qs-clothing-assets', 'integrations/labels.json'))
-- labels.male.components['11'] → every published top for the male ped
-- { { collection = 'qsc_m', localIndex = 0, name = 'Los Santos Customs Tee' }, ... }
-- labels.female.props['0'] → every published hat for the female pedTo turn collection + localIndex into the number a menu uses, ask the game: GetPedDrawableGlobalIndexFromCollection(ped, slot, collection, localIndex) for clothing, GetPedPropGlobalIndexFromCollection for props.
Notifications#
Messages are shown as toasts in the studio's own style. To route them through your notify resource instead, edit the Notification function in custom/client.lua:
-- custom/client.lua
function Notification(msg, type)
if GetResourceState('qs-interface') == 'started' then
return exports['qs-interface']:AddNotify(msg, type, 5000)
end
SendReactMessage('notification', { msg = msg, type = type })
endtype is info, success or error. Server-side messages arrive through the same function.
Discord Logs#
Three channels, each posted as an embed. Leave a URL empty to switch that channel off:
Channel | What goes there |
|---|---|
| Addon clothing imports, and every settings change or reset with the admin who made it. |
| Approvals and rejections. |
| Failed builds, failed publish cycles, and items whose texture file was lost. |
-- server/webhooks.lua
Webhooks = {
publish = 'https://discord.com/api/webhooks/...',
moderation = '',
errors = 'https://discord.com/api/webhooks/...',
}Every publish, retire and rebuild is also recorded in the
qs_clothing_publishestable — who did it, when, how long it took and whether it worked — whether or not a webhook is set.
Image Hosts#
With Settings → Images → Allow any host switched off, image links are only accepted from the hosts listed in custom/config.lua. Add your own CDN to the list:
-- custom/config.lua
Config.Images.AllowedHosts = {
'i.imgur.com', 'imgur.com', 'cdn.discordapp.com', 'media.discordapp.net',
'r2.fivemanage.com', 'api.fivemanage.com', 'i.ibb.co', 'raw.githubusercontent.com',
'cdn.yourserver.com',
}Links are always fetched by the server, never by a player's game, and anything that is not a PNG, JPG, GIF or WebP is refused.
Other Scripts During a Publish#
Two things are worth knowing if you run scripts that work with peds or routing buckets:
Routing buckets. While a new declaration is applied live, every player is moved into a private routing bucket (
870000+ their server id) — a driver alone in their vehicle takes it along — and goes back to their own bucket as soon as their game has the new clothing, within 30 seconds at most. Avoid using buckets in that range.Freemode peds. A live declaration needs every game free of freemode models after the refresh. A script that keeps its own freemode ped alive — a character-selection preview, a clone in a menu, a mannequin — keeps that game from taking the change; the publish then reports it and the declaration waits for a restart or an empty server.
Adding a Framework#
Supporting a framework that is not on the list takes one folder and one line:
Copy the closest bridge
Duplicate
custom/standalone(orcustom/esx/custom/qb) ascustom/myframework.Fill in the functions
server.luaanswers who a player is and whether they are an admin;client.luamust callSetFrameworkReady()once the character is loaded.lua -- custom/myframework/server.lua function framework:getIdentifier(source) end -- the player's unique id function framework:playerIsAdmin(source) end -- true for staff function framework:getUserName(source) end -- first name, last name function framework:getPlayers() end -- every online source -- also: getPlayerFromId, getSourceFromIdentifier, getUserNameFromIdentifier -- and call OnPlayerLoaded(source) when a character loadsRegister it
In
config/config.lua, map your framework's resource name to the folder name, then restart:lua local frameworks = { ['es_extended'] = 'esx', ['qb-core'] = 'qb', ['qbx_core'] = 'qb', ['my_framework'] = 'myframework', }
The same table is where a renamed framework resource goes — ['my-esx'] = 'esx' — and types.lua documents every bridge function for your editor's autocomplete.
Languages#
41 complete translations ship in locales/: Arabic, Bengali, Bulgarian, Catalan, Chinese, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hebrew, Hindi, Hungarian, Indonesian, Italian, Japanese, Korean, Latvian, Lithuanian, Malay, Norwegian, Persian, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swahili, Swedish, Thai, Turkish, Ukrainian, Urdu and Vietnamese. Pick one in Settings → General → Language — it applies to everyone at once, with no restart. To change a text, edit the matching key in locales/<code>.json; the files are left open by Asset Escrow.



