Skip to Content
QBCore docs – powered by Nextra 4
ResourcesQB-Clothing - Clothing Store System

QB-Clothing - Clothing Store System

The qb-clothing resource provides a comprehensive clothing and appearance system for QBCore servers, featuring clothing stores, outfit management, barber shops, and fashion customization.

Overview

QB-Clothing creates a realistic fashion and appearance system with clothing stores, barber shops, outfit saving, and complete character customization. Players can purchase clothing, save outfits, and manage their appearance.

Key Features

  • Clothing Stores: Multiple clothing shop locations
  • Outfit Management: Save and load custom outfits
  • Barber Shops: Hair styling and facial hair options
  • Accessories: Hats, glasses, jewelry, and more
  • Seasonal Collections: Limited-time clothing items
  • Fashion Shows: Organized clothing events
  • Custom Clothing: Player-created designs
  • Outfit Sharing: Share outfits with other players

Installation

Prerequisites

  • QBCore Framework
  • qb-target (for interaction system)
  • qb-menu (for clothing menus)
  • qb-inventory (for clothing items)

Installation Steps

  1. Download the Resource
cd resources/[qb] git clone https://github.com/qbcore-framework/qb-clothing.git
  1. Database Setup
-- Clothing system tables CREATE TABLE IF NOT EXISTS `player_outfits` ( `id` int(11) NOT NULL AUTO_INCREMENT, `citizenid` varchar(50) DEFAULT NULL, `outfitname` varchar(50) DEFAULT NULL, `model` varchar(50) DEFAULT NULL, `skin` longtext DEFAULT NULL, `outfitId` varchar(50) DEFAULT NULL, PRIMARY KEY (`id`), KEY `citizenid` (`citizenid`), KEY `outfitId` (`outfitId`) ); CREATE TABLE IF NOT EXISTS `clothing_stores` ( `id` int(11) NOT NULL AUTO_INCREMENT, `store_name` varchar(100) DEFAULT NULL, `inventory` longtext DEFAULT NULL, `sales` int(11) DEFAULT 0, `last_restock` timestamp DEFAULT current_timestamp(), PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `clothing_purchases` ( `id` int(11) NOT NULL AUTO_INCREMENT, `citizenid` varchar(50) DEFAULT NULL, `item_type` varchar(50) DEFAULT NULL, `item_id` int(11) DEFAULT NULL, `price` int(11) DEFAULT 0, `store` varchar(100) DEFAULT NULL, `purchase_date` timestamp DEFAULT current_timestamp(), PRIMARY KEY (`id`) );
  1. Add Items to qb-core/shared/items.lua
-- Clothing Items ['clothing_shirt'] = { ['name'] = 'clothing_shirt', ['label'] = 'Shirt', ['weight'] = 100, ['type'] = 'item', ['image'] = 'clothing_shirt.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Stylish shirt for everyday wear' }, ['clothing_pants'] = { ['name'] = 'clothing_pants', ['label'] = 'Pants', ['weight'] = 150, ['type'] = 'item', ['image'] = 'clothing_pants.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Comfortable pants' }, ['clothing_shoes'] = { ['name'] = 'clothing_shoes', ['label'] = 'Shoes', ['weight'] = 200, ['type'] = 'item', ['image'] = 'clothing_shoes.png', ['unique'] = false, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Quality footwear' }, ['outfit_bag'] = { ['name'] = 'outfit_bag', ['label'] = 'Outfit Bag', ['weight'] = 50, ['type'] = 'item', ['image'] = 'outfit_bag.png', ['unique'] = true, ['useable'] = true, ['shouldClose'] = true, ['combinable'] = nil, ['description'] = 'Contains a complete outfit' }
  1. Add to server.cfg
ensure qb-clothing

Configuration

Basic Configuration

Config = {} -- General Settings Config.UseTarget = true Config.ClothingCost = 25 -- Base cost per clothing item Config.OutfitLimit = 25 -- Max outfits per player -- Clothing Store Locations Config.ClothingStores = { ["suburban"] = { label = "Suburban Clothing", coords = vector3(72.3, -1399.1, 29.4), blip = { sprite = 73, color = 47, scale = 0.8 }, clothing_types = {"casual", "business", "formal"}, price_modifier = 1.0, zones = { counter = vector3(73.96, -1392.46, 29.38), changing_room = vector3(75.95, -1387.79, 29.38) } }, ["ponsonbys"] = { label = "Ponsonbys", coords = vector3(-703.78, -152.3, 37.42), clothing_types = {"luxury", "designer", "formal"}, price_modifier = 2.5, zones = { counter = vector3(-707.88, -153.27, 37.42), changing_room = vector3(-710.15, -149.36, 37.42) } }, ["discount"] = { label = "Discount Store", coords = vector3(-821.91, -1073.25, 11.33), clothing_types = {"casual", "basic"}, price_modifier = 0.6, zones = { counter = vector3(-823.08, -1072.36, 11.33), changing_room = vector3(-819.02, -1078.95, 11.33) } } } -- Barber Shop Locations Config.BarberShops = { ["herr_kutz"] = { label = "Herr Kutz Barber", coords = vector3(-814.3, -183.8, 37.6), blip = { sprite = 71, color = 4, scale = 0.8 }, services = { haircut = {price = 150, label = "Haircut"}, beard_trim = {price = 75, label = "Beard Trim"}, full_service = {price = 200, label = "Full Service"} } }, ["beach_barber"] = { label = "Beach Combover", coords = vector3(-1282.6, -1116.8, 7.0), services = { haircut = {price = 120, label = "Haircut"}, beard_trim = {price = 60, label = "Beard Trim"} } } } -- Clothing Categories Config.ClothingCategories = { ["casual"] = { label = "Casual Wear", items = { tshirt = {min_price = 25, max_price = 75}, jeans = {min_price = 40, max_price = 120}, sneakers = {min_price = 60, max_price = 150} } }, ["business"] = { label = "Business Attire", items = { dress_shirt = {min_price = 80, max_price = 200}, suit_pants = {min_price = 120, max_price = 300}, dress_shoes = {min_price = 150, max_price = 400} } }, ["formal"] = { label = "Formal Wear", items = { suit_jacket = {min_price = 300, max_price = 800}, dress = {min_price = 200, max_price = 600}, formal_shoes = {min_price = 200, max_price = 500} } }, ["luxury"] = { label = "Luxury Fashion", items = { designer_shirt = {min_price = 500, max_price = 1500}, designer_pants = {min_price = 800, max_price = 2000}, luxury_shoes = {min_price = 1000, max_price = 3000} } } } -- Outfit Presets Config.OutfitPresets = { ["business_suit"] = { label = "Business Suit", male = { ["arms"] = {item = 4, texture = 0}, ["t-shirt"] = {item = 31, texture = 0}, ["torso2"] = {item = 4, texture = 0}, ["pants"] = {item = 10, texture = 0}, ["shoes"] = {item = 10, texture = 0} }, female = { ["arms"] = {item = 3, texture = 0}, ["t-shirt"] = {item = 6, texture = 0}, ["torso2"] = {item = 7, texture = 0}, ["pants"] = {item = 6, texture = 0}, ["shoes"] = {item = 6, texture = 0} }, price = 1500 }, ["casual_outfit"] = { label = "Casual Outfit", male = { ["arms"] = {item = 0, texture = 0}, ["t-shirt"] = {item = 15, texture = 0}, ["torso2"] = {item = 0, texture = 0}, ["pants"] = {item = 1, texture = 0}, ["shoes"] = {item = 1, texture = 0} }, female = { ["arms"] = {item = 0, texture = 0}, ["t-shirt"] = {item = 2, texture = 0}, ["torso2"] = {item = 4, texture = 0}, ["pants"] = {item = 0, texture = 0}, ["shoes"] = {item = 0, texture = 0} }, price = 350 } }

Customization Options

-- Clothing Customization Config.ClothingComponents = { ["male"] = { ["face"] = {min = 0, max = 20, zone = "Head"}, ["skin"] = {min = 0, max = 11, zone = "Head"}, ["hair"] = {min = 0, max = 22, zone = "Head"}, ["hair_color"] = {min = 0, max = 63, zone = "Head"}, ["arms"] = {min = 0, max = 174, zone = "Arms"}, ["t-shirt"] = {min = 0, max = 168, zone = "Torso"}, ["torso2"] = {min = 0, max = 392, zone = "Torso"}, ["pants"] = {min = 0, max = 138, zone = "Legs"}, ["shoes"] = {min = 0, max = 98, zone = "Feet"}, ["chain"] = {min = 0, max = 121, zone = "Accessories"}, ["ears"] = {min = -1, max = 41, zone = "Accessories"}, ["glasses"] = {min = 0, max = 28, zone = "Accessories"}, ["hat"] = {min = -1, max = 154, zone = "Accessories"} }, ["female"] = { ["face"] = {min = 0, max = 20, zone = "Head"}, ["skin"] = {min = 0, max = 11, zone = "Head"}, ["hair"] = {min = 0, max = 23, zone = "Head"}, ["hair_color"] = {min = 0, max = 63, zone = "Head"}, ["arms"] = {min = 0, max = 161, zone = "Arms"}, ["t-shirt"] = {min = 0, max = 187, zone = "Torso"}, ["torso2"] = {min = 0, max = 370, zone = "Torso"}, ["pants"] = {min = 0, max = 149, zone = "Legs"}, ["shoes"] = {min = 0, max = 96, zone = "Feet"}, ["chain"] = {min = 0, max = 96, zone = "Accessories"}, ["ears"] = {min = -1, max = 41, zone = "Accessories"}, ["glasses"] = {min = 0, max = 28, zone = "Accessories"}, ["hat"] = {min = -1, max = 154, zone = "Accessories"} } } -- Hair Styles Config.HairStyles = { ["male"] = { [0] = "Bald", [1] = "Buzzcut", [2] = "Faux Hawk", [3] = "Hipster", [4] = "Side Part", [5] = "Pompadour", [6] = "Long Hair", [7] = "Curly", [8] = "Military", [9] = "Dreads" }, ["female"] = { [0] = "Bald", [1] = "Short Pixie", [2] = "Bob Cut", [3] = "Long Straight", [4] = "Beach Waves", [5] = "Ponytail", [6] = "Braids", [7] = "Curly Updo", [8] = "Side Bangs", [9] = "Messy Bun" } }

API Reference

Client Exports

OpenClothingMenu

Open clothing store interface.

exports['qb-clothing']:OpenClothingMenu(storeId) -- Parameters: -- storeId: ID of clothing store

SaveOutfit

Save current outfit.

local success = exports['qb-clothing']:SaveOutfit(outfitName) -- Parameters: -- outfitName: Name for the outfit -- Returns: boolean success status

LoadOutfit

Load a saved outfit.

local success = exports['qb-clothing']:LoadOutfit(outfitId) -- Parameters: -- outfitId: ID of outfit to load -- Returns: boolean success status

Server Exports

GetPlayerOutfits

Get player’s saved outfits.

local outfits = exports['qb-clothing']:GetPlayerOutfits(citizenId) -- Parameters: -- citizenId: Player citizen ID -- Returns: table of saved outfits

CreateOutfitItem

Create outfit item for inventory.

local itemData = exports['qb-clothing']:CreateOutfitItem(outfitData) -- Parameters: -- outfitData: Outfit configuration -- Returns: outfit item data

UpdateClothingStore

Update store inventory.

exports['qb-clothing']:UpdateClothingStore(storeId, inventory) -- Parameters: -- storeId: Store identifier -- inventory: New inventory data

Events

Client Events

-- Clothing purchased RegisterNetEvent('qb-clothing:client:clothingPurchased', function(itemData) -- Handle clothing purchase end) -- Outfit saved RegisterNetEvent('qb-clothing:client:outfitSaved', function(outfitName, outfitId) -- Handle outfit saving end) -- Appearance changed RegisterNetEvent('qb-clothing:client:appearanceChanged', function(componentData) -- Handle appearance change end)

Server Events

-- Store sale recorded RegisterNetEvent('qb-clothing:server:saleRecorded', function(storeId, saleData) -- Handle store sale end) -- Outfit created RegisterNetEvent('qb-clothing:server:outfitCreated', function(citizenId, outfitData) -- Handle outfit creation end) -- Barber service completed RegisterNetEvent('qb-clothing:server:barberServiceCompleted', function(citizenId, serviceType, cost) -- Handle barber service end)

Usage Examples

Clothing Store System

-- Client-side clothing store interface RegisterNetEvent('qb-clothing:client:openStore', function(storeId) local storeConfig = Config.ClothingStores[storeId] if not storeConfig then QBCore.Functions.Notify("Store not found", "error") return end -- Open clothing menu local clothingMenu = { { header = storeConfig.label, txt = "Browse our collection", isMenuHeader = true }\n }\n \n -- Add clothing categories\n for category, categoryData in pairs(Config.ClothingCategories) do\n if HasStoreCategory(storeConfig.clothing_types, category) then\n table.insert(clothingMenu, {\n header = categoryData.label,\n txt = \"Browse \" .. categoryData.label:lower(),\n params = {\n event = \"qb-clothing:client:browseCategory\",\n args = {\n store = storeId,\n category = category\n }\n }\n })\n end\n end\n \n -- Add outfit presets\n table.insert(clothingMenu, {\n header = \"Complete Outfits\",\n txt = \"Ready-to-wear outfit sets\",\n params = {\n event = \"qb-clothing:client:browseOutfits\",\n args = {\n store = storeId\n }\n }\n })\n \n -- Add my outfits management\n table.insert(clothingMenu, {\n header = \"My Outfits\",\n txt = \"Manage saved outfits\",\n params = {\n event = \"qb-clothing:client:manageOutfits\"\n }\n })\n \n exports['qb-menu']:openMenu(clothingMenu)\nend)\n\n-- Browse clothing category\nRegisterNetEvent('qb-clothing:client:browseCategory', function(data)\n local storeConfig = Config.ClothingStores[data.store]\n local categoryData = Config.ClothingCategories[data.category]\n \n -- Create category menu\n local categoryMenu = {\n {\n header = categoryData.label,\n txt = \"Price range varies by item\",\n isMenuHeader = true\n }\n }\n \n -- Add clothing items\n for itemType, itemData in pairs(categoryData.items) do\n local basePrice = math.random(itemData.min_price, itemData.max_price)\n local finalPrice = math.floor(basePrice * storeConfig.price_modifier)\n \n table.insert(categoryMenu, {\n header = FormatItemName(itemType),\n txt = \"$\" .. finalPrice .. \" - Try on and customize\",\n params = {\n event = \"qb-clothing:client:tryOnItem\",\n args = {\n store = data.store,\n category = data.category,\n item_type = itemType,\n price = finalPrice\n }\n }\n })\n end\n \n exports['qb-menu']:openMenu(categoryMenu)\nend)\n\n-- Try on clothing item\nRegisterNetEvent('qb-clothing:client:tryOnItem', function(data)\n local ped = PlayerPedId()\n local currentClothing = GetCurrentClothing(ped)\n \n -- Enter clothing selection mode\n local clothingData = GetClothingOptions(data.item_type, GetEntityModel(ped))\n \n if clothingData then\n local selectionMenu = {\n {\n header = \"Customize \" .. FormatItemName(data.item_type),\n txt = \"Use arrow keys to browse options\",\n isMenuHeader = true\n },\n {\n header = \"Purchase Item\",\n txt = \"Buy for $\" .. data.price,\n params = {\n event = \"qb-clothing:client:purchaseItem\",\n args = data\n }\n },\n {\n header = \"Cancel\",\n txt = \"Return to browsing\",\n params = {\n event = \"qb-clothing:client:cancelTryOn\",\n args = {original_clothing = currentClothing}\n }\n }\n }\n \n exports['qb-menu']:openMenu(selectionMenu)\n \n -- Start clothing preview loop\n StartClothingPreview(data.item_type, clothingData)\n end\nend)\n\nfunction StartClothingPreview(itemType, clothingData)\n local currentIndex = 1\n local maxIndex = #clothingData\n \n CreateThread(function()\n while previewMode do\n Wait(1)\n \n -- Navigation controls\n if IsControlJustPressed(0, 174) then -- Left arrow\n currentIndex = currentIndex - 1\n if currentIndex < 1 then currentIndex = maxIndex end\n ApplyClothingItem(itemType, clothingData[currentIndex])\n elseif IsControlJustPressed(0, 175) then -- Right arrow\n currentIndex = currentIndex + 1\n if currentIndex > maxIndex then currentIndex = 1 end\n ApplyClothingItem(itemType, clothingData[currentIndex])\n end\n end\n end)\nend\n```\n\n### Outfit Management System\n\n```lua\n-- Outfit saving and loading\nRegisterNetEvent('qb-clothing:client:saveOutfit', function()\n local input = exports['qb-input']:ShowInput({\n header = \"Save Current Outfit\",\n submitText = \"Save Outfit\",\n inputs = {\n {\n text = \"Outfit Name\",\n name = \"outfit_name\",\n type = \"text\",\n isRequired = true,\n default = \"\"\n }\n }\n })\n \n if input and input.outfit_name then\n local ped = PlayerPedId()\n local currentSkin = GetCurrentSkin(ped)\n local model = GetEntityModel(ped)\n \n TriggerServerEvent('qb-clothing:server:saveOutfit', {\n name = input.outfit_name,\n model = model,\n skin = currentSkin\n })\n end\nend)\n\n-- Server-side outfit saving\nRegisterNetEvent('qb-clothing:server:saveOutfit', function(outfitData)\n local src = source\n local player = QBCore.Functions.GetPlayer(src)\n \n if not player then return end\n \n -- Check outfit limit\n local currentOutfits = MySQL.scalar.await('SELECT COUNT(*) FROM player_outfits WHERE citizenid = ?', {\n player.PlayerData.citizenid\n })\n \n if currentOutfits >= Config.OutfitLimit then\n TriggerClientEvent('QBCore:Notify', src, 'You have reached the maximum number of saved outfits', 'error')\n return\n end\n \n -- Generate unique outfit ID\n local outfitId = GenerateUniqueId()\n \n -- Save outfit to database\n MySQL.insert('INSERT INTO player_outfits (citizenid, outfitname, model, skin, outfitId) VALUES (?, ?, ?, ?, ?)', {\n player.PlayerData.citizenid,\n outfitData.name,\n outfitData.model,\n json.encode(outfitData.skin),\n outfitId\n })\n \n TriggerClientEvent('QBCore:Notify', src, 'Outfit \"' .. outfitData.name .. '\" saved successfully!', 'success')\nend)\n\n-- Load outfit\nRegisterNetEvent('qb-clothing:client:loadOutfit', function(outfitId)\n QBCore.Functions.TriggerCallback('qb-clothing:server:getOutfit', function(outfitData)\n if outfitData then\n local ped = PlayerPedId()\n local skinData = json.decode(outfitData.skin)\n \n -- Apply outfit\n ApplyCompleteOutfit(ped, skinData)\n \n QBCore.Functions.Notify('Outfit \"' .. outfitData.outfitname .. '\" loaded!', 'success')\n else\n QBCore.Functions.Notify('Outfit not found', 'error')\n end\n end, outfitId)\nend)\n\nfunction ApplyCompleteOutfit(ped, skinData)\n -- Apply each clothing component\n for component, data in pairs(skinData) do\n if component == \"face\" then\n SetPedHeadBlendData(ped, data.item, data.item, 0, data.texture, data.texture, 0, 1.0, 1.0, 0.0, false)\n elseif component == \"hair\" then\n SetPedComponentVariation(ped, 2, data.item, data.texture, 0)\n elseif component == \"hair_color\" then\n SetPedHairColor(ped, data.item, data.texture)\n else\n local componentId = GetComponentId(component)\n if componentId then\n if componentId >= 0 and componentId <= 11 then\n SetPedComponentVariation(ped, componentId, data.item, data.texture, 0)\n else\n SetPedPropIndex(ped, componentId - 12, data.item, data.texture, true)\n end\n end\n end\n end\nend\n```\n\n### Barber Shop System\n\n```lua\n-- Barber shop services\nRegisterNetEvent('qb-clothing:client:openBarberShop', function(shopId)\n local shopConfig = Config.BarberShops[shopId]\n if not shopConfig then\n QBCore.Functions.Notify(\"Barber shop not found\", \"error\")\n return\n end\n \n local barberMenu = {\n {\n header = shopConfig.label,\n txt = \"Professional grooming services\",\n isMenuHeader = true\n }\n }\n \n -- Add services\n for serviceType, serviceData in pairs(shopConfig.services) do\n table.insert(barberMenu, {\n header = serviceData.label,\n txt = \"$\" .. serviceData.price,\n params = {\n event = \"qb-clothing:client:selectBarberService\",\n args = {\n shop = shopId,\n service = serviceType,\n price = serviceData.price\n }\n }\n })\n end\n \n exports['qb-menu']:openMenu(barberMenu)\nend)\n\n-- Select barber service\nRegisterNetEvent('qb-clothing:client:selectBarberService', function(data)\n if data.service == \"haircut\" then\n OpenHairStylingMenu(data)\n elseif data.service == \"beard_trim\" then\n OpenBeardStylingMenu(data)\n elseif data.service == \"full_service\" then\n OpenFullGroomingMenu(data)\n end\nend)\n\nfunction OpenHairStylingMenu(serviceData)\n local ped = PlayerPedId()\n local currentHair = GetPedDrawableVariation(ped, 2)\n local currentColor = GetPedHairColor(ped)\n local model = GetEntityModel(ped)\n local gender = (model == GetHashKey(\"mp_m_freemode_01\")) and \"male\" or \"female\"\n \n local hairMenu = {\n {\n header = \"Hair Styling\",\n txt = \"Choose your new look\",\n isMenuHeader = true\n }\n }\n \n -- Add hair styles\n for hairId, hairName in pairs(Config.HairStyles[gender]) do\n table.insert(hairMenu, {\n header = hairName,\n txt = \"Preview this style\",\n params = {\n event = \"qb-clothing:client:previewHair\",\n args = {\n hair_id = hairId,\n service_data = serviceData\n }\n }\n })\n end\n \n table.insert(hairMenu, {\n header = \"Confirm & Pay\",\n txt = \"$\" .. serviceData.price,\n params = {\n event = \"qb-clothing:client:payForService\",\n args = serviceData\n }\n })\n \n exports['qb-menu']:openMenu(hairMenu)\nend\n```\n\n## Troubleshooting\n\n### Common Issues\n\n#### Clothing Not Applying\n```lua\n-- Check component validity\nfunction ValidateClothingComponent(ped, component, drawable, texture)\n local maxDrawable = GetNumberOfPedDrawableVariations(ped, component) - 1\n local maxTexture = GetNumberOfPedTextureVariations(ped, component, drawable) - 1\n \n if drawable < 0 or drawable > maxDrawable then\n print(\"Invalid drawable for component:\", component, \"Drawable:\", drawable)\n return false\n end\n \n if texture < 0 or texture > maxTexture then\n print(\"Invalid texture for component:\", component, \"Texture:\", texture)\n return false\n end\n \n return true\nend\n```\n\n#### Outfit Loading Issues\n- Verify outfit data structure in database\n- Check model compatibility between save and load\n- Ensure proper JSON encoding/decoding\n\n#### Store Menu Problems\n```lua\n-- Debug store configuration\nRegisterCommand('debugstore', function(source, args)\n local storeId = args[1]\n if storeId and Config.ClothingStores[storeId] then\n local store = Config.ClothingStores[storeId]\n print(\"Store:\", store.label)\n print(\"Categories:\", json.encode(store.clothing_types))\n print(\"Price modifier:\", store.price_modifier)\n else\n print(\"Store not found:\", storeId)\n end\nend)\n```\n\n### Debug Commands\n\n```lua\n-- Give clothing item\n/giveclothing [player_id] [clothing_type]\n\n-- Reset player appearance\n/resetappearance [player_id]\n\n-- Load outfit by ID\n/loadoutfit [outfit_id]\n\n-- Check saved outfits\n/checkoutfits [player_id]\n```\n\n<Callout type=\"info\">\n Regular updates to clothing catalogs and seasonal collections help maintain player engagement with the fashion system.\n</Callout>
Last updated on