# Clothing Integration > Appearance script integration guide for ML Inventory Category: INVENTORY · Source: https://miciomods.it/docs/ml-inventory-cloth-integration · Last updated: 2026-10-02 # 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 appearance script at startup and writes to it when a clothing item is equipped or unequipped, and when the inventory closes. 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. Worn clothing is written back into `illenium-appearance`, `bl_appearance`, `4bit_appearance` and `codem-appearance` (also detected under its other name, `crm-appearance`). `fivem-appearance` and `qb-clothing` are detected as well, but nothing is written back into them: on those two the outfit lives in the inventory and in the appearance script's own storage independently. When no appearance script is running, the outfit lives in the inventory only, and the sync entry points still bring an outfit in. ## Sync entry points Call either client entry point from the appearance script when an outfit is saved or changed. ```lua exports.ml_clothing:syncFromNUI(appearanceData) ``` ```lua TriggerEvent('ml_clothing:client:syncFromNUI', appearanceData) ``` The server translates the appearance through the data adapters. Client synchronization preserves existing collection items and their server metadata, including bag IDs, carrier IDs, plates and durability. A client appearance cannot create, replace or remove a collection item. A collection request aimed at an occupied legacy slot also leaves that slot unchanged. Global drawable items continue to follow the appearance; omitted legacy slots can be cleared. 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: ```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. A dropped sync prints `syncNewOutfit blocked` in the server console when `Config.Debug = true`. ## Collection items ```lua local metadata = { collection = 'your_collection', localDrawable = 0, texture = 0, sexRaw = 'male', } ``` Use the exact collection name and a valid local drawable and texture for the installed pack. Existing items with global drawable metadata remain supported. Collection bag sizes, armor class and component requirements are set in Configuration. ## Trusted server outfit sync ```lua local success, reason = exports.ml_clothing:SyncOutfit(playerId, appearanceData) ``` The calling resource must be started, the player must be loaded, and no outfit sync may be in progress. The server derives the player sex. Validate purchases, permissions and allowed clothing before calling the export. Do not forward unchecked client appearances. ```lua local appearanceData = { components = { { component_id = 9, collection = 'your_collection', localDrawable = 0, texture = 0 }, }, props = { { prop_id = 0, collection = 'your_collection', localDrawable = 0, texture = 0 }, }, } ``` The export updates the complete outfit and may clear omitted equipment slots. It returns `true` on completion or `false` with a reason. The sync is not atomic; earlier slots may have changed before a failure. ```lua local reasons = { 'unauthorized_resource', 'invalid_player', 'player_not_loaded', 'invalid_appearance', 'sync_busy', 'sync_incomplete', 'sync_failed', } ``` `sync_incomplete` means an inventory removal or insertion failed. Check the current outfit and inventory before deciding whether to retry. ## Trust model A sync carries whatever the appearance script hands over, so that script's save flow is the trust boundary. The server keeps the payload inside fixed limits before anything is written: - The load gate above. - One sync per player per second. - One sync at a time per player. - An entry whose drawable falls outside 0 to 65535 or whose texture falls outside 0 to 255 is dropped, not corrected. Inside those bounds the value is taken as sent, so a drawable the ped model does not have is passed through. - An entry is ignored unless its component or prop id maps to a known clothing item. - That item is placed only in the slot its own definition names. A component whose drawable and texture match the default appearance for the character's sex produces no item and leaves the slot empty. ## Data adapters Each appearance format has its own translator file in `open/adapters/`. The adapters in the folder: - `standard.lua` reads `components` and `props` entries built from `component_id` or `prop_id`, `drawable` and `texture`. This is the default shape and it covers `illenium-appearance`. Collection entries also accept collection and localDrawable; drawable is optional for those entries. - `bl_appearance.lua` reads `drawables` and `props` entries built from `index`, `value` and `texture`. An adapter turns the appearance payload into entries of `{ type, id, drawable, texture, collection, localDrawable }` where `type` is `component` or `prop`, and returns an empty table when the payload is not its format. The server walks the registered adapters and takes the first one that returns entries. The order is not guaranteed, so two adapters must never both match the same payload. If a custom payload also carries `components`, edit `standard.lua` to narrow it or delete the file, so only one adapter can claim the payload. 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 = {} return standardClothes end ``` - `Config.DataAdapters` table of adapter functions, default `{}` in a new adapter file and filled by every adapter file in the folder. Each key is the adapter name, each value the adapter function. > **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. ### 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 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) ``` ### Clothing shop Inside `OpenShop`, in the customization callback: **client/client.lua** ```lua 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 end Framework.CachePed() end, config) ``` ### Outfit change In the handler for `illenium-appearance:client:changeOutfit`, in the branch that saves: **client/client.lua** ```lua else local appearance = client.getPedAppearance(cache.ped) TriggerServerEvent("illenium-appearance:server:saveAppearance", appearance) exports.ml_clothing:syncFromNUI(appearance) -- add this line end ``` ## codem-appearance Saving worn clothing back into `codem-appearance` is automatic: after a clothing item is equipped or unequipped, and when the inventory closes, 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. The other direction needs no edit to the appearance script: opening the inventory reads the live ped and syncs it, so an outfit picked in its menus lands in the clothing slots at the next inventory open. To get an outfit into the slots sooner, call `syncFromPed` from your own code once the appearance has been applied to the ped: ```lua exports.ml_clothing:syncFromPed() ``` ## 4bit_appearance Worn clothing is written back into `4bit_appearance` automatically. The outfit to items direction is not automatic and has no inventory-open fallback: call `exports.ml_clothing:syncFromPed()` from your own code after its menu has applied the appearance to the ped. ## bl_appearance The release ships built, with no `src/`. Apply the two edits below to the deployed `dist/client/init.js`, or make them in a source checkout and rebuild with its own build script. The file names below are the source paths; the same two calls are in the built file. In `src/client/appearance/setters.ts`, as the last line of `setPlayerPedAppearance`: **src/client/appearance/setters.ts** ```typescript export async function setPlayerPedAppearance(data) { setPedHairColors(ped, data.hairColor) setPedTattoos(ped, data.tattoos) emit("ml_clothing:client:syncFromNUI", data) // add this line } ``` In `src/client/handlers.ts`, inside the save callback, after the save round trip has produced the validated appearance: **src/client/handlers.ts** ```typescript 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, as the last line of the function. ## Verify **shared/config.lua** ```lua Config.Debug = false ``` - `Config.Debug` default `false`. Set it to `true` for the console prints used below, back to `false` once the check is done. With debug on: 1. Restart `ml_clothing` after editing `shared/config.lua`, and restart the appearance script after adding the export call. For `bl_appearance`, restart it after the rebuild or after editing the deployed bundle. 2. Change outfit in the appearance menu and save. 3. The clothing slots in the inventory update to the new outfit and the player gets the outfit updated notification. Nothing is printed on success: the console only logs refused syncs. A notification alone does not prove that an adapter matched. Existing collection items can remain unchanged after an accepted client sync. Check the expected global drawable changes or the trusted server export result. If the console prints `syncNewOutfit blocked` instead, the player was not fully loaded yet; wait a few seconds in game and save again. The gate opens once the inventory and the character id read stable after the character loads. If it never opens, the console names the reason with `Config.Debug = true`: the inventory was not stable within ten seconds, or a character switch was detected. Rejoining reopens the gate.