What you are referring to is called serialization. I would check out the tutorial I linked, as it explains it much better than I can.
In the words of @starmaq because apparently the quote block isn’t working:
“By definition, serialization is taking a piece of data that can’t be saved or transfered through the network, and turning it into a piece of data that can be saved or transfered through the network.”
What this means is that while you may not be able to save the actual character instances in-game, you can however save the information that you need to re-create that character.
For starters, there are two ways you can go about obtaining the information you need so you can save the character appearance. The first way is to create your own character customization system in-game. This would be easier to understand as all the data you use to customize the character is already accessible to you. However, because we cannot save roblox instances directly we must save the data that can be used to re-contruct the instance. While we may not be able to save the shirt instance we can save the Shirt | Roblox Creator Documentation property the shirt uses. This means that you could store the data in a table like in this example:
local characterAppearance = {
Shirt = 1205935 -- a random number to indicate an asset id.
}
To get the shirt content id you could use a simple function like so:
local function FilterShirtContentId(shirt)
return tonumber(string.match(shirt.ShirtTemplate, "%d+") or 0) -- use string patterns to automatically parse the string to get the number
end
Then you could use said function to create a table with the current shirt texture the player has equipped. We can rewrite our example from earlier to use this function:
local characterAppearance = {
Shirt = FilterShirtContentId(shirt)
}
Great! Now we have a table that can be saved in a datastore and used to re-create texture a shirt instance to the saved texture. However, while the data we have is useable, we don’t have a method for creating a shirt from this data. Because we have the content id however, this is relatively easy. All we have to do is get or create a shirt object for a player and set it’s properties. So our function might look something like this:
local LoadShirt(player, contentId)
local character = player.Character or player.CharacterAppearanceLoaded:Wait()
local shirt = character:FindFirstChildWhichIsA("Shirt") or Instance.new("Shirt")
shirt.ShirtTemplate = "rbxassetid://"..tostring(contentId)
shirt.Parent = character
end
And there we have it.
The second method is easier to implement and more versatile but ultimately requires more setup and a greater understanding. It involves sorting through the contents of Players | Roblox Creator Documentation and using Player | Roblox Creator Documentation.
Lastly, if you do not understand how to save data then I reccomend checking out some tutorials roblox provides. They can be really helpful.