Tutorial · Gameplay

How to make character customization in Roblox Studio

Roleplay games, obbies with skins and RPGs all let players change how their character looks. Roblox has one object for the whole look of a character, HumanoidDescription: body colours, body scales, clothing, accessories and animations. This guide lets players pick a skin colour, a height and clothing from lists you control, and applies the change on the server so everyone sees it.

Updated 23 October 20265 min readIntermediateLuau

In short

On the server, get the character's current look with humanoid:GetAppliedDescription(), change the properties you want (such as HeadColor and the other body colours, HeightScale or Shirt), then call humanoid:ApplyDescriptionAsync(description). A RemoteEvent carries the player's choice from a menu; the server only accepts values from its own lists.

  • Start from the applied description, not a new one, or the character loses everything you did not set.
  • Apply on the server so every player sees the new look.
  • Only accept colours, sizes and asset IDs from server-side lists, so nobody can wear anything you did not offer.

Get the script

Character customization server script

A Script in ServerScriptService that changes a player's skin colour, height, shirt or pants from allowed lists with HumanoidDescription.

Script in ServerScriptService named Customization
-- Script in ServerScriptService named Customization.
-- The client asks for a change; the server only accepts colours, sizes and clothing
-- from the lists below, then applies them with a HumanoidDescription.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- What players may choose. Add clothing asset IDs from the Creator Store or Marketplace.
local SKIN_COLORS: { Color3 } = {
	Color3.fromRGB(255, 220, 177),
	Color3.fromRGB(234, 184, 146),
	Color3.fromRGB(198, 134, 66),
	Color3.fromRGB(141, 85, 36),
	Color3.fromRGB(90, 56, 37),
	Color3.fromRGB(110, 170, 255),
}
local SHIRTS: { number } = { 0 } -- 0 means no shirt; add shirt asset IDs here
local PANTS: { number } = { 0 }
local MIN_HEIGHT, MAX_HEIGHT = 0.9, 1.1 -- R15 height scale range
local COOLDOWN = 1 -- seconds between changes

local changeEvent = Instance.new("RemoteEvent")
changeEvent.Name = "ChangeLook"
changeEvent.Parent = ReplicatedStorage

local lastChange: { [Player]: number } = {}

local function setAllBodyColors(description: HumanoidDescription, color: Color3)
	description.HeadColor = color
	description.TorsoColor = color
	description.LeftArmColor = color
	description.RightArmColor = color
	description.LeftLegColor = color
	description.RightLegColor = color
end

changeEvent.OnServerEvent:Connect(function(player: Player, kind: unknown, value: unknown)
	if typeof(kind) ~= "string" or typeof(value) ~= "number" or value ~= value then
		return
	end
	local now = os.clock()
	if now - (lastChange[player] or -math.huge) < COOLDOWN then
		return
	end
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	if not humanoid or humanoid.Health <= 0 then
		return
	end
	lastChange[player] = now

	-- Start from what the character wears now, and change one thing
	local description = humanoid:GetAppliedDescription()
	local index = math.floor(value)
	if kind == "skin" and SKIN_COLORS[index] then
		setAllBodyColors(description, SKIN_COLORS[index])
	elseif kind == "shirt" and SHIRTS[index] then
		description.Shirt = SHIRTS[index]
	elseif kind == "pants" and PANTS[index] then
		description.Pants = PANTS[index]
	elseif kind == "height" then
		description.HeightScale = math.clamp(value, MIN_HEIGHT, MAX_HEIGHT)
	else
		return
	end
	local ok, err = pcall(function()
		humanoid:ApplyDescriptionAsync(description)
	end)
	if not ok then
		warn("Customization: could not apply look for", player.Name, err)
	end
end)

Players.PlayerRemoving:Connect(function(player: Player)
	lastChange[player] = nil
end)

RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts

Where to put it

Explorer: the server script and the menu
ServerScriptService
  Customization Script new
StarterPlayer
  StarterPlayerScripts
    CustomizeMenu LocalScript new

Settings you can change

SHIRTSIn the script
Shirt asset IDs players may choose; 0 means no shirt. PANTS works the same way. Default: { 0 }.
MIN_HEIGHT, MAX_HEIGHTIn the script
The range of the height scale for R15 characters. Default: 0.9, 1.1.
COOLDOWNIn the script
Seconds between changes for one player. Default: 1.

What a cheater can and cannot doThe client sends only a choice number or a height. The server picks the colour or asset ID from its own lists, clamps the height and limits how often a player can change, so a cheater cannot put on items you did not offer.

Type-checked with luau-lsp strict against Roblox's API definitions on 23 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.

Prompt for RoCode

Add character customization: a menu with 6 skin colour swatches and Short, Normal and Tall buttons, and a server Script that takes the choice through a RemoteEvent, changes the player's HumanoidDescription from GetAppliedDescription (all body colours, or HeightScale clamped between 0.9 and 1.1), and applies it with ApplyDescriptionAsync.

On this page
  1. Get the script
  2. How it works
  3. 1. Server
  4. 2. Menu
  5. 3. Keep the look
  6. Common errors
  7. With RoCode
  8. Questions
  9. Sources

How HumanoidDescription works

A HumanoidDescription lists everything about a character's look. GetAppliedDescription returns a copy of the one the character has now; you change the copy and apply it. Roblox then updates only what changed. The older ApplyDescription is superseded by ApplyDescriptionAsync for new work, which is what this guide uses.

Properties this guide changes
PropertyWhat it is
HeadColor, TorsoColor, LeftArmColor and the restBody part colours, as Color3.
HeightScaleHeight for R15 characters. Scale properties do not affect R6.
Shirt, PantsClothing asset IDs; 0 removes the item.

Step 1: The server script

Insert a Script in ServerScriptService named Customization:

Script in ServerScriptService named Customization
-- Script in ServerScriptService named Customization.
-- The client asks for a change; the server only accepts colours, sizes and clothing
-- from the lists below, then applies them with a HumanoidDescription.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- What players may choose. Add clothing asset IDs from the Creator Store or Marketplace.
local SKIN_COLORS: { Color3 } = {
	Color3.fromRGB(255, 220, 177),
	Color3.fromRGB(234, 184, 146),
	Color3.fromRGB(198, 134, 66),
	Color3.fromRGB(141, 85, 36),
	Color3.fromRGB(90, 56, 37),
	Color3.fromRGB(110, 170, 255),
}
local SHIRTS: { number } = { 0 } -- 0 means no shirt; add shirt asset IDs here
local PANTS: { number } = { 0 }
local MIN_HEIGHT, MAX_HEIGHT = 0.9, 1.1 -- R15 height scale range
local COOLDOWN = 1 -- seconds between changes

local changeEvent = Instance.new("RemoteEvent")
changeEvent.Name = "ChangeLook"
changeEvent.Parent = ReplicatedStorage

local lastChange: { [Player]: number } = {}

local function setAllBodyColors(description: HumanoidDescription, color: Color3)
	description.HeadColor = color
	description.TorsoColor = color
	description.LeftArmColor = color
	description.RightArmColor = color
	description.LeftLegColor = color
	description.RightLegColor = color
end

changeEvent.OnServerEvent:Connect(function(player: Player, kind: unknown, value: unknown)
	if typeof(kind) ~= "string" or typeof(value) ~= "number" or value ~= value then
		return
	end
	local now = os.clock()
	if now - (lastChange[player] or -math.huge) < COOLDOWN then
		return
	end
	local character = player.Character
	local humanoid = character and character:FindFirstChildOfClass("Humanoid")
	if not humanoid or humanoid.Health <= 0 then
		return
	end
	lastChange[player] = now

	-- Start from what the character wears now, and change one thing
	local description = humanoid:GetAppliedDescription()
	local index = math.floor(value)
	if kind == "skin" and SKIN_COLORS[index] then
		setAllBodyColors(description, SKIN_COLORS[index])
	elseif kind == "shirt" and SHIRTS[index] then
		description.Shirt = SHIRTS[index]
	elseif kind == "pants" and PANTS[index] then
		description.Pants = PANTS[index]
	elseif kind == "height" then
		description.HeightScale = math.clamp(value, MIN_HEIGHT, MAX_HEIGHT)
	else
		return
	end
	local ok, err = pcall(function()
		humanoid:ApplyDescriptionAsync(description)
	end)
	if not ok then
		warn("Customization: could not apply look for", player.Name, err)
	end
end)

Players.PlayerRemoving:Connect(function(player: Player)
	lastChange[player] = nil
end)

The client sends a kind ("skin", "shirt", "pants" or "height") and a number. For colours and clothing the number is a position in the server's list, so a request for item 99 finds nothing and is ignored. Height is clamped to the allowed range. ApplyDescriptionAsync waits while Roblox loads any new assets and can fail, so it runs inside pcall.

Step 2: The customize menu

Insert a LocalScript in StarterPlayer > StarterPlayerScripts named CustomizeMenu:

LocalScript in StarterPlayerScripts named CustomizeMenu
-- LocalScript in StarterPlayerScripts named CustomizeMenu.
-- Skin colour swatches and height buttons. Each click asks the server to change the look.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local changeEvent = ReplicatedStorage:WaitForChild("ChangeLook") :: RemoteEvent

-- The same colours as the server's SKIN_COLORS, in the same order, for the swatches
local SWATCHES: { Color3 } = {
	Color3.fromRGB(255, 220, 177),
	Color3.fromRGB(234, 184, 146),
	Color3.fromRGB(198, 134, 66),
	Color3.fromRGB(141, 85, 36),
	Color3.fromRGB(90, 56, 37),
	Color3.fromRGB(110, 170, 255),
}
local HEIGHTS = { 0.9, 1, 1.1 }

local gui = Instance.new("ScreenGui")
gui.Name = "CustomizeGui"
gui.ResetOnSpawn = false
local panel = Instance.new("Frame")
panel.AnchorPoint = Vector2.new(0, 1)
panel.Position = UDim2.new(0, 12, 1, -12)
panel.Size = UDim2.fromOffset(300, 120)
panel.BackgroundColor3 = Color3.fromRGB(30, 26, 40)
panel.Parent = gui
local panelCorner = Instance.new("UICorner")
panelCorner.CornerRadius = UDim.new(0, 12)
panelCorner.Parent = panel

local function addButton(position: UDim2, size: UDim2, color: Color3, text: string, onClick: () -> ())
	local b = Instance.new("TextButton")
	b.Position = position
	b.Size = size
	b.BackgroundColor3 = color
	b.Text = text
	b.TextColor3 = Color3.new(1, 1, 1)
	b.Font = Enum.Font.GothamBold
	b.TextSize = 14
	local c = Instance.new("UICorner")
	c.CornerRadius = UDim.new(0, 8)
	c.Parent = b
	b.Activated:Connect(onClick)
	b.Parent = panel
end

for i, color in SWATCHES do
	addButton(UDim2.fromOffset(12 + (i - 1) * 46, 14), UDim2.fromOffset(38, 38), color, "", function()
		changeEvent:FireServer("skin", i)
	end)
end
for i, height in HEIGHTS do
	local label = if height < 1 then "Short" elseif height > 1 then "Tall" else "Normal"
	addButton(UDim2.fromOffset(12 + (i - 1) * 94, 66), UDim2.fromOffset(86, 40), Color3.fromRGB(110, 80, 220), label, function()
		changeEvent:FireServer("height", height)
	end)
end

gui.Parent = Players.LocalPlayer:WaitForChild("PlayerGui")

The swatches use the same colours in the same order as the server's list, and send their position. Keep the two lists in step when you change one. For a longer menu, see the shop GUI guide for scrolling lists and the UI animations guide for hover effects.

Step 3: Keep the look after a respawn

A respawn builds a new character from the player's own avatar, so changes are lost. To keep them, store the player's choices on the server (in a table, or attributes on the Player), and in player.CharacterAdded apply them again with the same code. To keep them between sessions too, save the choices with the DataStore guide.

Test it

  1. Press Play. Click a swatch: after a moment your character's skin changes colour.
  2. Click Tall: your character grows. With an R6 avatar nothing happens, as scales only affect R15.
  3. Start a 2-player test: the other player sees your new look.
  4. Click swatches very fast: changes apply at most once a second.

Common errors and fixes

Check the Output window on the Server view for warnings from Customization.

Customization: could not apply look

ApplyDescriptionAsync failed, often because an asset ID in the description does not exist or could not load. Check the IDs in SHIRTS and PANTS.

Height does nothing

The character is R6. Set the game to R15 in Game Settings, Avatar, or use colours and clothing only.

My hat and hair disappeared

The description was built with Instance.new("HumanoidDescription") instead of GetAppliedDescription(). Always start from the applied one.

The change only shows for me

It was applied by a LocalScript. Apply it on the server, as Customization does.

Build it with RoCode

If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts and GUI in your Explorer.

You type

Add a character customizer with skin colours, three heights and four shirts I give you the IDs for, applied on the server and kept after respawning.

RoCode does
  • Writes the server script with your allowed lists and the menu that matches them
  • Is told to compile-check each script with Check Script, and sets undo points around each batch
  • Can save the choices with your existing data script if you have one

RoCode does not choose or upload clothing for you: pick the asset IDs yourself. After a build its test robot can click the menu in Studio and report PASS or FAIL, but it checks only what it chose to test. How RoCode connects to Studio.

Questions

How do I change a player's character in Roblox?

On the server, take humanoid:GetAppliedDescription(), change its properties, and apply it with humanoid:ApplyDescriptionAsync().

How do I change skin colour with a script?

Set HeadColor, TorsoColor, LeftArmColor, RightArmColor, LeftLegColor and RightLegColor on the description to the same Color3, then apply it.

Why does my HumanoidDescription change not work?

Apply it on the server, start from GetAppliedDescription, and check the Output: ApplyDescriptionAsync can fail on a bad asset ID.

Can players wear any item they type in?

Not with this script: the server only accepts IDs from its own lists. Letting players enter any ID means checking it is a real item of the right type first.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.