Cosmetics SDK — building cosmetic packs
The cosmetic side of the RivalityFX SDK. A cosmetic pack is a self-contained resource that adds items to the customization screen by registering a plain-data descriptor with the SDK, in one of three kinds:
- skins (
kind='skin') — whole ped models; - outfits (
kind='outfit') — streamed add-on clothing dressed onto a freemode base ped; - weapon wraps (
kind='weaponTex') — a global weapon tint.
Skins and outfits share the appearance axis — one equip slot, one player pref, one "Appearance" catalogue; a player wears a ped-model skin or an outfit, never both. Wraps are a second axis with their own pref. It is the exact mirror of the mode SDK: the same registry pattern, a second domain.
One platform SDK, two contracts:
exports.sdk:registerMode(gameplay) andexports.sdk:registerPack(cosmetics). Each is versioned independently.
The model
- The
sdkresource owns the contract, the pack registries, and thedefinePackbuilder. Registration runs as a shared script, so the descriptor lands in the SDK's client registry (render + apply) and its server registry (the equip-gating authority). customizationis a consumer: it mirrors the client registry to render the page and apply cosmetics. Preferences are client KVP (no database); equipping is server-gated against ownership (entitlements).- A pack is its own resource that depends on
sdk(the contract) andcustomization(the runtime), and registers viaexports.sdk:registerPack.
The base packs ship as an OSS example (cosmetic_pack_base, base-game models,
MIT). Two paid-pack templates (also MIT) ship their own streamed assets under
stream/ and register through the same public SDK — no first-party shortcut:
cosmetic_pack_neon (streamed ped models) and cosmetic_pack_vanguard (streamed
add-on clothing / outfits). That is exactly what lets anyone add packs without forking
the core.
Build a pack in 3 steps
-
Scaffold a resource that depends on the SDK + customization:
-- fxmanifest.lua dependencies { 'sdk', 'customization' } shared_scripts { '@sdk/shared/kit.lua', -- SDK helpers the contract uses (load first) '@sdk/shared/cosmetics.lua', -- definePack + LuaCATS types 'pack.lua', -- SHARED: registers on the client AND the server } -
Author the pack and register it:
-- pack.lua local PACK = definePack{ id = 'mypack', apiVersion = 2, kind = 'skin', status = 'owned', access = 'player', present = { title = 'My Pack', sub = 'Included', rgb = '34 211 230' }, skins = { { id = 'a_m_m_skater_01', label = 'Skater', rarity = 'common' }, }, } -- Register now AND re-announce on a live `restart sdk`: Sdk.autoRegister(function() exports.sdk:registerPack(PACK) end) -
Ship assets (streamed packs only): drop the model files in
stream/, named to match each skinid. Base-game ids stream nothing. (Outfit packs ship clothing, not models — theirstream/layout and manifest wiring differ; see below.)
Outfits — streamed clothing
A kind='outfit' pack does not swap the ped model: each item dresses a freemode
base ped with add-on clothing the pack streams, addressed by collection + local
drawable index so it can never collide with other packs or shift on a gamebuild
bump. It is the appearance kind built for a PvP arena — every player stays on the same
rig (same hitbox), only the garments change.
Outfit packs have real asset-pipeline requirements — a cloth tool, ShopPedApparel
.meta wiring in the manifest, an Element Club subscription on the server — so they
get their own guide:
Authoring a streamed outfit pack. The
descriptor fields are in the
pack descriptor;
the template is cosmetic_pack_vanguard. Verify a mounted collection in-game with
/collections, and test equips with /skin <id> (the appearance axis is one command —
skin or outfit id).
Weapon wraps
Beyond appearances, a pack can ship weapon wraps (kind='weaponTex') — a global
weapon cosmetic that re-tints every weapon the player carries (the loadout stays
arena-level; the look is per-player), Fortnite-style. A wrap is plain data too: a tint
(a built-in GTA weapon tint index) + a swatch (the card colour). The OSS example packs
liveries (free) and precious (VIP) ship in cosmetic_pack_base.
definePack{
id = 'liveries', kind = 'weaponTex', status = 'owned',
present = { title = 'Liveries', sub = 'Included', rgb = '120 200 160' },
skins = {
{ id = 'wrap_army', label = 'Army', tint = 4, swatch = '106 122 90' },
{ id = 'wrap_gold', label = 'Gold', tint = 2, swatch = '212 175 55', rarity = 'vip' },
},
}customization persists the equipped wrap as a second player pref
(rfx:pref:weaponwrap); the engine applies it at match
spawn (SetPedWeaponTintIndex, after the loadout is given). Ownership/gating reuse the
same kind-agnostic server equip-gate as appearances, so a wrap pack is free / VIP /
sku exactly like a skin pack. Test with /wrap <id> · /wraps. Fields: see the
pack descriptor.
Thumbnails
Cosmetic cards show a thumbnail derived from a single admin convar
rfx_assets_url (the public assets base — see server.cfg):
<base>/peds/<id>.webp for a ped skin, <base>/outfits/<id>.webp for an outfit,
<base>/weapons/<id>.webp for a streamed weapon texture. Set an item's image to
override; a built-in wrap uses its swatch instead.
Reference
- registerPack — the registration exports + validation.
- Pack descriptor — every field.
- Authoring a streamed outfit pack — the asset + manifest workflow for
kind='outfit'. - Presentation block — hero art +
info/buylinks for a pack card. - customization — the consumer (catalogue render + apply).
- engine-sdk — the gameplay sibling (modes).