Convert decal ID to image ID (2025)

Heyo!

Here’s the code

-- Please credit me @mastawba
local escapecharacters = {lt = "<",gt = ">",qout = '"',apot = "'",amp = "&"}
local urlIdExtractor = {
	"http://www.roblox.com/asset/%?id=(%d+)",
	"https://www.roblox.com/asset/%?id=(%d+)",
	"rbxassetid://(%d+)",
	"rbxthumb://type=Asset&id=(%d+).*"
}
local function freecharacters(chars : string)
	local new = chars:gsub("&.-;",function(a)
		return escapecharacters[a:sub(2, -2)] or a
	end)
	return new
end
local function parseTag(line)
	local tag, attributes_line = line:match("<([%w/:]+)%s(.-)>")
	if not tag then -- Tag with no attribute
		tag = line:match("<([%w/:]+)>")
	end
	local attributes = {}
	if attributes_line ~= nil then
		for key, value in attributes_line:gmatch("([%w:]+)=(\".-\")") do
			attributes[key] = freecharacters(value:sub(2, -2)) -- Remove quotes
		end
	end
	return tag, attributes
end
type XMLTag = {children : {XMLTag | string}, tag : string, tag_attributes : {}}
function ParseXML(XML : string) : {XMLTag | string}
	local current = {children = {}}
	local stack = {current}
	local XMLBuffer = buffer.fromstring(XML)
	local lastlt, lastt = nil, 0
	local function extractText(pointer)
		if lastt == nil then return end
		local textData = buffer.readstring(XMLBuffer,lastt,pointer - lastt)
		if textData:match("%S") then table.insert(current.children,freecharacters(textData)) end
		lastt = nil
	end
	for pointer = 0,buffer.len(XMLBuffer) - 1 do
		local byte = buffer.readu8(XMLBuffer,pointer)
		if byte == 60 then -- <
			extractText(pointer)
			lastlt = pointer 
		elseif byte == 62 then -- >
			lastt = pointer + 1
			local tag = buffer.readstring(XMLBuffer,lastlt,pointer - lastlt + 1)
			if tag:sub(1,2) == "<?" and tag:sub(#tag-1,#tag) == "?>" then -- xml declaration
			elseif tag:sub(1,4) == "<!--" and tag:sub(#tag-2,#tag) == "-->" then -- comment
			elseif tag:sub(1,9) == "<!DOCTYPE" then -- doctype
			else
				if tag:sub(2,2) == "/" then -- closing tag
					table.remove(stack)
					current = stack[#stack]
				else
					local tag_name, tag_attributes = parseTag(tag)
					local newChild = {
						tag = tag_name,
						tag_attributes = tag_attributes,
						children = {}
					}
					table.insert(current.children,newChild)
					table.insert(stack,newChild)
					current = newChild
				end
			end
			lastlt = nil
		end
	end
	extractText(buffer.len(XMLBuffer))
	return current.children
end
local function GetTag(start : {XMLTag | string},name,attributesToLook : {string:any}?) : XMLTag?
	attributesToLook = attributesToLook or {}
	local cc = 0; for _ in attributesToLook do cc += 1 end
	for _,child in start do
		if type(child) == "table" and child.tag == name then
			local c1 = 0; for name,value in child.tag_attributes do if attributesToLook[name] == value then c1 += 1 end end
			local c2 = 0; for name,value in attributesToLook do if child.tag_attributes[name] == value then c2 += 1 end end
			if c1 == cc and c2 == cc then return child end
		end
	end
end
function GetImageIdFromDecalId(AssetId : string) : number
	if type(AssetId) ~= "number" then
		error("Invalid argument #1 (number expected, got "..typeof(AssetId)..")")
	end
	local Work, ParsedResponse = pcall(function()
		return ParseXML(game:GetService("HttpService"):GetAsync("https://assetdelivery.roproxy.com/v1/asset/?id="..AssetId))
	end)
	if not Work then
		error("Failed to get parsed XML response from Roblox",22)
	end
	local roblox = GetTag(ParsedResponse,"roblox")
	local Item = roblox and GetTag(roblox.children,"Item",{class = "Decal"})
	local DecalProperties = Item and GetTag(Item.children,"Properties")
	local content = DecalProperties and GetTag(DecalProperties.children,"Content",{name = "Texture"})
	local urltag = content and GetTag(content.children,"url")
	if urltag then
		local url = urltag.children[1]
		if type(url) == "string" then
			for _,extractor in urlIdExtractor do
				local id = tonumber(url:match(extractor))
				if id then
					return id
				end
			end
		end
	else
		error("Not a decal Id",2)
	end
end
return {
	GetImageIdFromDecalId = GetImageIdFromDecalId
}

DECAL ID TO IMAGE ID

Lemme get this straight, I can totally relate to some developers that it’s so frustrating to get the image id from decal id ESPECIALLY IF YOU HAVE A SYSTEM WITH DECAL INPUT TEXTBOX FOR USERS. Even worse when Roblox has changed on how their asset ids works to be semantic which makes it even more difficult.

hehehe i made a very simple and easy way to actually get the image id from decal id. Just by putting the code into ModuleScript for example and then this. Best part is it works in live production servers. (I tested it guaranteed)

local GetImageIdFromDecalId = require(script.GetImageIdFromDecalId)

print(GetImageIdFromDecalId.GetImageIdFromDecalId(128747854507573)) --> 97504313148702

Just to be sure, please wrap it in pcall

local GetImageIdFromDecalId = require(script.GetImageIdFromDecalId)

local work, decalid = pcall(function()
    return GetImageIdFromDecalId.GetImageIdFromDecalId(128747854507573)
end)
print(work, decalid) --> true, 97504313148702

I COOKED​:fire::fire::fire:SO HOW DID I DO IT?

This silly url https://assetdelivery.roblox.com/v1/asset/?id=id returns some very useful data about the asset Id.

Now ofc you can’t use this url because Roblox doesn’t let you use their resource or whatever they say.

Now ofc I fixed it by using roproxy https://assetdelivery.roproxy.com/v1/asset/?id=id

For example when I GET this url https://assetdelivery.roblox.com/v1/asset/?id=128747854507573 it returns this

<roblox xmlns:xmime="http://www.w3.org/2005/05/xmlmime" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.roblox.com/roblox.xsd" version="4">
  <External>null</External>
  <External>nil</External>
  <Item class="Decal" referent="RBX0">
    <Properties>
      <token name="Face">5</token>
      <string name="Name">Decal</string>
      <float name="Shiny">20</float>
      <float name="Specular">0</float>
      <Content name="Texture">
        <url>http://www.roblox.com/asset/?id=97504313148702</url>
      </Content>
      <bool name="archivable">true</bool>
    </Properties>
  </Item>
</roblox>

Now this particular XML is a representation of a Decal instance written in XML for some reason. We can see its properties such as Face, Name, Shiny, Specular, archiveable, and TEXTURE!!

TEXTURE that’s what we are looking for because it contains a url linked to the image id!!

So then I wrote a short comprehensive code that parses XML to lua table.

Then with that parsed XML thing, the code will try to get the Texture URL then format it to a number id thingy.

Happy “custom decal game hangout” 'ing!

7 Likes

YES YOU INDEED COOKED :fire:
This is straight fire and genius, can’t wait to see this on future roblox projects!

1 Like

You are also able to use InsertService:LoadAsset on any public decal to get its associated ImageId; it returns the Decal instance with the Texture set to the Image URI, this is actually what the XML you are getting back is trying to represent. When using this method, you should ensure not to directly parent the instance, though, and just read the properties as some Decals may have non-decal instances in them, which may be malicious.

I haven’t yet got a chance to check if the InsertService alternative only works for Creator Store distributed decals or literally any non-private decal. Although, regardless, the method does avoid using a proxy, so it may be more ideal in cases where data integrity is really important. The method you show should work, though, and may support more decals (assuming LoadAsset only works on distributed decals), so it’s nice to see another solution to the issue!

2 Likes

I certainly did not know InsertService:LoadAsset would work :sob: didn’t know it existed either. Thank you for showing me that lol.

Well it’s gonna be useful if you don’t have access to creator dashboard at the time, because you can find there images with the ID’s in page URL. But it’ll be more useful to have a function to find multiple id’s at a time.

Anyway, I respect your hard work and I’ll try to have a try some day!

what’s better than mine?