# Clothing Integration > Appearance script integration guide for ML Clothing System Category: INVENTORY · Source: https://miciomods.it/docs/ml-inventory-cloth-integration · Last updated: 2026-07-13 # Clothing Integration ML Clothing keeps worn clothes and inventory items in sync in two directions. Saving worn clothing back to the appearance script is automatic: the resource detects the running appearance script at startup and writes through its save path. The opposite direction, turning an outfit picked in an appearance menu into inventory items, needs one line added to the appearance script at every point where an outfit is saved. This page shows where to add that line for `illenium-appearance` and `bl_appearance`, and how to adapt any other appearance script. ## Sync entry points Call either of these from the appearance script whenever a player saves or changes an outfit. Both send the appearance data to the server, which translates it through the data adapters and places the matching clothing items into the dedicated inventory slots. ```lua exports.ml_clothing:syncFromNUI(appearanceData) ``` ```lua TriggerEvent('ml_clothing:client:syncFromNUI', appearanceData) ``` When the appearance data of the script is not available or its format has no adapter, read the live ped instead. The call below captures the current components and props after the appearance script has applied them, so it works with any appearance system: ```lua exports.ml_clothing:syncFromPed() ``` > **INFO:** Load gate > > The server drops syncs that arrive during the first seconds after a character loads, while the inventory is still rebinding. This protects against item corruption on login and character switch. A dropped sync prints `syncNewOutfit blocked` in the server console when `Config.Debug = true`. Normal menu flows finish well after the gate opens. ## Data adapters The server does not care which appearance script produced the data. Each shipped format has its own translator file in `open/adapters/`: `illenium.lua`, `bl_appearance.lua`, and `native.lua`. An adapter turns the appearance payload into entries of `{ type, id, drawable, texture }` where `type` is `component` or `prop`; the first adapter that returns entries wins. To support a different appearance script, drop a new file in the folder and call one of the sync entry points from that script: **open/adapters/custom_appearance.lua** ```lua Config.DataAdapters = Config.DataAdapters or {} function Config.DataAdapters.custom_appearance(appearanceData) local standardClothes = {} -- translate the outfit data into entries like: -- standardClothes[#standardClothes + 1] = { type = 'component', id = 11, drawable = 15, texture = 0 } return standardClothes end ``` > **WARNING:** New files need a refresh > > A file added to the resource is only picked up after `refresh` followed by `ensure ml_clothing`. A plain restart without `refresh` skips new files. ## illenium-appearance Three functions in `client/client.lua` save an outfit. Add the export call right after each `illenium-appearance:server:saveAppearance` trigger. Unchanged lines are collapsed with `-- ...`. ### Character creation Covers the starting clothes of a fresh character. Without this call the outfit picked during character creation never becomes inventory items. **client/client.lua** ```lua function InitializeCharacter(gender, onSubmit, onCancel) -- ... client.startPlayerCustomization(function(appearance) if (appearance) then TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance) exports.ml_clothing:syncFromNUI(appearance) -- add this line if onSubmit then onSubmit() end elseif onCancel then onCancel() end -- ... end, config) end ``` ### Clothing shop **client/client.lua** ```lua function OpenShop(config, isPedMenu, shopType) lib.callback("illenium-appearance:server:hasMoney", false, function(hasMoney, money) -- ... client.startPlayerCustomization(function(appearance) if appearance then if not isPedMenu then TriggerServerEvent("illenium-appearance:server:chargeCustomer", shopType) end TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance) exports.ml_clothing:syncFromNUI(appearance) -- add this line else -- ... end Framework.CachePed() end, config) end, shopType) end ``` ### Outfit change **client/client.lua** ```lua RegisterNetEvent("illenium-appearance:client:changeOutfit", function(data) -- ... if data.disableSave then -- ... else local appearance = client.getPedAppearance(cache.ped) TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance) exports.ml_clothing:syncFromNUI(appearance) -- add this line end -- ... end) ``` ## codem-appearance Detection covers the resource under both of its common names, `crm-appearance` and `codem-appearance`. Saving worn clothing back to its database is automatic: after every inventory equip or unequip, ML Clothing calls the `crm_save_appearance` export, which reads the live ped and writes its own row. Nothing to set up for that direction. Because the resource is escrowed, its files cannot be edited. Wire the outfit-to-items direction from the scripts on your side that call its documented hooks. Character creation, in the multicharacter script: ```lua TriggerEvent('crm-appearance:init-new-character', 'crm-male', function() exports.ml_clothing:syncFromPed() end) ``` Any custom flow that saves through the export: ```lua exports['crm-appearance']:crm_save_appearance(nil, function() exports.ml_clothing:syncFromPed() end) ``` The menus opened with `crm-appearance:show-clothing-menu`, `show-outfits`, and `show-barber-menu` do not expose a save callback, so there is no hook point for those flows. Outfits changed there reach the inventory the next time a flow with a callback runs, or when the player equips something through the inventory. ## bl_appearance `bl_appearance` is TypeScript. Edit the two spots below in `src/client/` and rebuild with `pnpm run init`, or apply the same lines directly to the deployed `dist/client/init.js`. In `src/client/appearance/setters.ts`, as the last line of `setPlayerPedAppearance`: **src/client/appearance/setters.ts** ```javascript export async function setPlayerPedAppearance(data) { // ... setPedHairColors(ped, data.hairColor) setPedTattoos(ped, data.tattoos) emit("ml_clothing:client:syncFromNUI", data) // add this line, keep it last } ``` In `src/client/handlers.ts`, inside the save callback, after the save round trip has produced the validated appearance: **src/client/handlers.ts** ```javascript RegisterNuiCallback(Receive.save, async (appearance, cb) => { // ... const newAppearance = await getAppearance(ped) newAppearance.tattoos = appearance.tattoos || null triggerServerCallback("bl_appearance:server:saveAppearance", getFrameworkID(), newAppearance) emit("ml_clothing:client:syncFromNUI", newAppearance) // add this line setPedTattoos(ped, newAppearance.tattoos) closeMenu() cb(1) }) ``` > **WARNING:** Emit position > > Placing the emit at the top of `setPlayerPedAppearance` sends the raw menu payload. Out of range drawables from presets or missing addon packs would then be written into item metadata and spam `SetVariation` errors at the next login. Keep the emit after the setters have run. ## Verify 1. Set `Config.Debug = true` in `shared/config.lua` and restart `ml_clothing`. 2. Change outfit in the appearance menu and save. 3. The clothing slots in the inventory update to the new outfit and the server console logs the sync. If the console prints `syncNewOutfit blocked` instead, the player was not fully loaded yet; wait a few seconds in game and save again.