[Update] January 26, 2026
Hi Creators!
We’re happy to announce that EditableImage support for SurfaceAppearance is now available in Studio! You can now use the EditableImage API to create your own custom Color, Metalness, Roughness, and/or Normal maps to create a SurfaceAppearance using the new runtime AssetService.CreateSurfaceAppearanceAsync method.

EditableImage being used to modify the various maps that SurfaceAppearance supports, allowing a player to paint with a glossy yellow color.
To try out this new functionality, go to File > Beta Features > and enable “EditableImage support for SurfaceAppearance maps”
Getting Started
After enabling the Studio Beta, you can use the new AssetService.CreateSurfaceAppearanceAsync method by providing a table of EditableImage Content objects as input.
Here’s an example demonstrating how to create and apply a custom SurfaceAppearance to a MeshPart:
Click here to expand
game.Lighting.EnvironmentSpecularScale = 1 -- make sure we allow reflective surfaces
local InsertService = game:GetService("InsertService")
local meshPart = InsertService:CreateMeshPartAsync("rbxassetid://16799342206", Enum.CollisionFidelity.Box, Enum.RenderFidelity.Automatic)
meshPart.TextureID = "rbxassetid://16799393274"
meshPart.Size = Vector3.new(10,10,10)
meshPart.Parent = workspace
wait(5) -- make a dramatic pause
local AssetService = game:GetService("AssetService")
local ei1 = AssetService:CreateEditableImage({Size=Vector2.new(256, 256)})
local ei3 = AssetService:CreateEditableImage({Size=Vector2.new(4, 4)})
local ei4 = AssetService:CreateEditableImage({Size=Vector2.new(8, 8)})
-- purple albedo
ei1:DrawRectangle(Vector2.zero, ei1.Size, Color3.new(0.82, 0, 0.82), 0, Enum.ImageCombineType.Overwrite)
-- full metal
ei3:DrawRectangle(Vector2.zero, ei3.Size, Color3.new(1, 1, 1), 0, Enum.ImageCombineType.Overwrite)
-- 0 roughness
ei4:DrawRectangle(Vector2.zero, ei4.Size, Color3.new(0, 0, 0), 0, Enum.ImageCombineType.Overwrite)
local sa = AssetService:CreateSurfaceAppearanceAsync({
ColorMap = Content.fromObject(ei1),
-- NormalMap is not specified
NormalMap = nil,
MetalnessMap = Content.fromObject(ei3),
RoughnessMap = Content.fromObject(ei4)
})
sa.Parent = workspace.MeshPart
Purple ColorMap + full metalness + zero roughness creates a very shiny object that reflects the environment. Note the Fresnel reflection on the side face.
The script provided above should be placed in a LocalScript parented to StarterPlayerScripts to ensure it runs correctly.
Keep in mind that if you leave a map as nil or do not provide an input for it, the SurfaceAppearance will be created without that map, using a default value instead (see code example + resulting texture above). This means you do not need to provide inputs for all four maps (ColorMap, NormalMap, MetalnessMap, RoughnessMap) if they are not needed.
Known Issues and Limitations
- Only EditableImage Content can be used: The API currently only supports
EditableImage Contentas input. We are working on adding support to mixEditableImage ContentwithContentfrom Asset URIs, but for now, you can use published image assets with this new SurfaceAppearance method by converting them into anEditableImagefirst:
local myEditableImage = AssetService:CreateEditableImageAsync(Content.fromUri("rbxassetid://ASSET_ID"))
-
No Mipmaps are generated: This runtime, async capability is meant for in-experience creation and is not intended to replace the asset system. When a
SurfaceAppearanceis created with this API, it will not have mipmaps. If you choose to publish thisSurfaceAppearancelater, it will go through the full asset upload process, which includes mipmap generation. -
No new compression: The images are not compressed using this new API. While this won’t add additional memory pressure compared to just using
EditableImageobjects, it’s something to be aware of.
Things to Keep in Mind
-
Note that the
CreateSurfaceAppearanceAsyncmethod is asynchronous. Although it may return almost immediately at the moment, this could change in the future. -
SurfaceAppearanceinstances created with this API will have a higher memory footprint compared to Studio-created ones. They are heavier on GPU memory (up to 12x) and use more GPU memory bandwidth because their constituent textures are not compressed or optimally packed for efficiency. Please adjust your use accordingly. -
The
SurfaceAppearanceinstance is driven by its underlyingEditableImageobjects. Please keep in mind the existing best practices for working withEditableImageobjects, especially regarding memory management, permissions, and Trust & Safety.
What’s Next
Our plan is to enable this for live experiences once we’ve gotten some initial feedback from you and feel confident about the API. We’re eager to hear your thoughts and suggestions on this new feature. Your feedback is what helps us improve!
Best,
Roblox Rendering Team

