In this tutorial, we will explore how to create a Luau module script that retrieves and lists properties for a specific class. Whether you’re working on a game, plugin, or any Luau-based project where classes play a crucial role, knowing how to extract and work with class properties is essential. I know there are different modules that provide this but I just wanted to write a tutorial explaining on how it is done.
First thing, it cannot be done without HTTP Service. So go to the Game Settings → Security → Allow HTTP Requests and turn it on
The way this is going to work is using HTTP Service to return a JSON table, decoding it and then sorting it for different classes allowing you have to have access to the properties. It is really simple
Let’s Begin with a simple module
We will define some variables such as
- HTTP Service →
local HTTPService = game:GetService("HttpService")
- The api url →
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"
That URL leads to a page with a JSON table which contains every Enum, Property and Class of an instance.
We can also add a Classes
table since we will store every class in it. Since we will be using :GetAsync and it yields, there isn’t a point of calling :GetAsync for every class, we will just use it once, it will sort the data and fill the Classes
table with every class and its properties and then retrieve it using the table.
Thus will yield only once instead of as many times as we want get the properties of an instance. I hope that made sense.
Then we will have a different function that will process and sort that data. Let’s call it ProcessAPIData
Now, Let’s make a simple module, that will return a function which will return a string array with the names of the class’ properties we want, with the things from above
local HTTPService = game:GetService("HttpService")
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"
local Classes = {}
function GetAPIData()
end
function ProcessAPIData()
end
return function(ClassName: string) : {string}
return Classes[ClassName]
end
Let’s start with the GetAPIData
function.
Incase something goes wrong, we will put GetAsync in a pcall()
(protective call). So if there are any errors, the script won’t stop running.
Inside of the pcall()
we will put an anonymous function that will just return the HTTPService:GetAsync
with the URL
function GetAPIData()
local success, response = pcall(function()
return HttpService:GetAsync(URL) -- that will put the data, returned from the URL, inside of the response variable
end)
--[[now we will check if it was successful of not.
if not, then we will wait 3 seconds, for example, and return the GetAPIData.
That will create a loop until it is successful.
If it is successful we will just return the response.
-- ]]
if success then
return response
else
warn(response) -- warning the error (for debugging purposes)
task.wait(3)
return GetAPIData()
end
end
and our GetAPIData()
function is ready.
Let’s move to the ProcessAPIData
function.
Let’s first get that API data using the GetAPIData
function
local apiData = GetAPIData()
This data will be a JSON formated table, we will have to decode it using the HTTPService:JSONDecode()
function.
apiData = HTTPService:JSONDecode(apiData)
or your code so far should look something like this
local HttpService = game:GetService("HttpService")
local Classes = {}
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"
function GetAPIData()
local success, response = pcall(function()
return HttpService:GetAsync(URL) -- that will put the data, returned from the URL, inside of the response variable
end)
--[[now we will check if it was successful of not.
if not, then we will wait 3 seconds, for example, and return the GetAPIData.
That will create a loop until it is successful.
If it is successful we will just return the response.
-- ]]
if success then
return response
else
warn(response) -- warning the error (for debugging purposes)
task.wait(3)
return GetAPIData()
end
end
function ProcessAPIData()
local apiData = GetAPIData()
apiData = HTTPService:JSONDecode(apiData)
end
return function(ClassName: string) : {string}
return Classes[ClassName]
end
If we print the apiData from the ProcessAPIData function, it will print a really long array, over 4000 key-value pairs.
The values from this array are tables. They can be 3 variants - for Classes, Properties and Enums.
For Classes should look something like this:
["Name"] = "VirtualInputManager",
["Superclass"] = "Instance",
["tags"] = {},
["type"] = "Class"
For Properties, something like this:
["Class"] = "Vector3Value",
["Name"] = "Value",
["ValueType"] = "Vector3",
["tags"] = {},
["type"] = "Property"
And we don’t care about Enums.
Now, we know how the api data looks. We can start “sorting” it.
First we will iterate/loop though the array
and let’s call the value an “entry”.
Since it is an array, we will use ipairs (I am not 100% sure about it, but I think it is a little bit faster than pairs when we are talking about arrays)
for _, entry in ipairs(apiData) do
end
now lets check the type, to see if it is a class or a property. We will use the type key from the “entry” table. So you can simply do
local entryType = entry.type
now let’s check if the entryType is a Class or a Property.
if entryType == "Class" then
elseif entryType == "Property" then
end
We are going to do first the Class.
this is how the “entry” should look (if you don’t remember)
["Name"] = "BasePart", -- the class name
["Superclass"] = "PVInstance", -- the super class
["tags"] = {}, -- tags
["type"] = "Class" -- the type
Let’s first explain what a Superclass is. A Superclass is basically a parent/base class which subclasses inherite its behavior.
In this case our class - BasePart is like a subclass of PVInstance.
And the PVInstance is the Superclass
I hope you understood.
As I said earlier, we will continue with the Class entryType.
let’s just save those values (from the entry) into variables
local className = entry.Name
local Superclass = entry.Superclass
let’s do a check if the Superclass exists.
if Superclass then
end
Now here gets a little confusing.
If it isn’t nil, we probably have it saved in the Classes
table.
so we can do
if Superclass then
local superclassData = Classes[Superclass]
end
if so then we can just iterate/loop though that data and assign the new class with the values since they will be exactly the same, since the class’ behavior is inherited from the Superclass’ one
if Superclass then
local superclassData = Classes[Superclass]
if superclassData then
for _, data in ipairs(superclassData) do
end
end
end
But how will we store that data?
Let’s move up a little bit and make a table right next to the className
and Superclass
variables.
local className = entry.Name
local Superclass = entry.Superclass
local classData = {}
now when we are iterating over the array, we can insert the values inside that classData table
for _, data in ipairs(superclassData) do
table.insert(classData, data)
end
and then we just insert that classData
into the Classes
table using className
as the key
Classes[className] = classData
and that’s it for the entryType Class
your function should be looking something like this so far
local function ProcessApiData()
local apiData = GetAPIData()
apiData = HttpService:JSONDecode(apiData)
for _, entry in ipairs(apiData) do
local entryType = entry.type
if entryType == "Class" then
local className = entry.Name
local classData = {}
local Superclass = entry.Superclass
if Superclass then
local superclassData = Classes[Superclass]
if superclassData then
for _, data in ipairs(superclassData) do
table.insert(classData, data)
end
end
end
Classes[className] = classData
elseif entryType == "Property" then
end
end
end
Let’s move onto the entryType Property
It is way too simple.
We know how the Property “entry” looks like (if somebody forgot)
["Class"] = "BasePart",
["Name"] = "BrickColor",
["ValueType"] = "BrickColor",
["tags"] = {},
["type"] = "Property"
We have the Class and the property name. We do not need anything else.
Let’s assign variables for some of them
local className = entry.Class
local propertyName = entry.Name
We have Class, the Property Name. So we can directly access the class by doing
Though first, I would personally want to check if there are no tags associated, so I will just do.
if not next(entry.tags) then
end
That will skip properties with tags.
And now we can just do inside of the check
local classData = Classes[className]
to access the classData
table which we will insert the propertyName
inside.
Let’s just check if the classData
isn’t nil and then we will insert it using table.insert
if classData then
table.insert(classData, propertyName)
end
and boom, your ProcessAPIData
function is done. It should look something like this:
local function ProcessApiData()
local apiData = GetAPIData()
apiData = HttpService:JSONDecode(apiData)
for _, entry in ipairs(apiData) do
local entryType = entry.type
if entryType == "Class" then
local className = entry.Name
local classData = {}
local Superclass = entry.Superclass
if Superclass then
local superclassData = Classes[Superclass]
if superclassData then
for _, data in ipairs(superclassData) do
table.insert(classData, data)
end
end
end
Classes[className] = classData
elseif entryType == "Property" then
local className = entry.Class
local propertyName = entry.Name
if next(entry.tags) then return end
local classData = Classes[className]
if classData then
table.insert(classData, propertyName)
end
end
end
end
now let’s call it at the end of the module. I personally would just let it yield instead of spawning it on another thread but that is just my preference.
And yeah, the module is done, really simple but effective. Here is how it should look
local HttpService = game:GetService("HttpService")
local URL = "https://anaminus.github.io/rbx/json/api/latest.json"
local Classes = {}
function GetAPIData()
local success, response = pcall(function()
return HttpService:GetAsync(URL)
end)
if success then
return response
else
warn(response)
task.wait(3)
return GetAPIData()
end
end
local function ProcessApiData()
local apiData = GetAPIData()
apiData = HttpService:JSONDecode(apiData)
for _, entry in ipairs(apiData) do
local entryType = entry.type
if entryType == "Class" then
local className = entry.Name
local Superclass = entry.Superclass
local classData = {}
if Superclass then
local superclassData = Classes[Superclass]
if superclassData then
for _, data in ipairs(superclassData) do
table.insert(classData, data)
end
end
end
Classes[className] = classData
elseif entryType == "Property" then
local className = entry.Class
local propertyName = entry.Name
if not next(entry.tags) then
local classData = Classes[className]
if classData then
table.insert(classData, propertyName)
end
end
end
end
end
ProcessApiData()
return function(ClassName: string)
return Classes[ClassName]
end
Now you may be wondering how to use it. It is fairly simple.
Just require the module
local module = require(path.to.module)
and just call it with the ClassName you want
module("BasePart")
and let’s print it
print(module("BasePart"))
This is what the output looks like:
{
[1] = "Archivable",
[2] = "Name",
[3] = "Parent",
[4] = "Anchored",
[5] = "BackParamA",
[6] = "BackParamB",
[7] = "BackSurface",
[8] = "BackSurfaceInput",
[9] = "BottomParamA",
[10] = "BottomParamB",
[11] = "BottomSurface",
[12] = "BottomSurfaceInput",
[13] = "BrickColor",
[14] = "CFrame",
[15] = "CanCollide",
[16] = "CollisionGroupId",
[17] = "Color",
[18] = "CustomPhysicalProperties",
[19] = "FrontParamA",
[20] = "FrontParamB",
[21] = "FrontSurface",
[22] = "FrontSurfaceInput",
[23] = "LeftParamA",
[24] = "LeftParamB",
[25] = "LeftSurface",
[26] = "LeftSurfaceInput",
[27] = "Locked",
[28] = "Material",
[29] = "Orientation",
[30] = "Position",
[31] = "Reflectance",
[32] = "RightParamA",
[33] = "RightParamB",
[34] = "RightSurface",
[35] = "RightSurfaceInput",
[36] = "RotVelocity",
[37] = "Rotation",
[38] = "Size",
[39] = "TopParamA",
[40] = "TopParamB",
[41] = "TopSurface",
[42] = "TopSurfaceInput",
[43] = "Transparency",
[44] = "Velocity"
}
This is the simplest way I could think of doing the module.
Hope you make a great use of it!