Group Ranker Module

Hello developers

Roblox has introduced an updated Communities Role Management system and new Cloud API endpoints related to it, which makes managing group roles a lot easier. One endpoint that really stood out to me is the Update Group Membership endpoint, which lets developers rank users directly through the API without needing to create external services.

Before this a lot of developers had to host ranking bots outside of Roblox. This module is designed to make that process simpler and provide a solution for ranking users using the new system.

This module is mainly designed for work roleplay and military games that require automated ranking systems to manage rank progression. For example, many roleplay groups use application center games where users complete quizzes to earn roles. When there are large numbers of applicants, manually ranking each user will be time consuming, so automated systems are commonly used to handle this process.

The goal of this module is to make transitioning from older systems as smooth as possible. Its core function follows the familiar structure of most games’ external ranking services, which typically involve the group ID, user ID, and rank. This module allows you to directly use the Roblox API without having to manually handle role IDs, membership IDs, etc..

To use this module you need to:

  • Have HTTP Requests enabled in your experience

  • Have a Roblox Cloud API key (it needs to have group:read and group:write permissions, and you need to have permission to rank users in said group.)

  • Store your API key in a Secret
    It’s recommended to name the secret “APIKey”, since that’s the default name used in the module. However, the secret name can be changed, as it’s an editable variable in the module.
    Note: To create a secret, go to your game’s Creator Dashboard and open the Secrets tab.

To set it up:

  1. Add the module to ServerScriptService

  2. Change any variables you’d like to change, these are the two editable variables and the line they are on:
    3 local DefaultGroupId = 12345 -- Set to your main group
    4 local SecretName = "APIKey" -- Set to your secret's name that you created

The module has the following functions:

Rank Module Functions

Keep in mind the GroupId variable is optional if you set the DefaultGroupId variable.

RankUser(UserId, TargetRank, GroupId) Ranks a user to specified rank within the group.

RankUserByName(UserId, RoleName, GroupId) Ranks a user using the role name instead of the rank number.

Promote(UserId, GroupId) Promotes a user to the next role based on the group’s role hierarchy.

Demote(UserId GroupId) Demotes a user to the previous role based on the group’s role hierarchy.

All functions have the same return format
success : boolean, result : string

This module gets roles dynamically and handles pagination internally. It works reliably even in large groups or groups with non-sequential ranks.

You can find the module here.

If you have any feedback, suggestions or run into any issues, feel free to reply below.
PS: This the first time i publish a model of any sort here. Hopefully I did well.

Module Source Code
local HttpService = game:GetService("HttpService")

local DefaultGroupId = 12345 -- Set to your main group
local SecretName = "APIKey" -- Set to your secret's name that you created.
-- Setting the DefaultGroupId variable will make the GroupID argument optional 
-- in all Ranker functions.

local ApiUrls = {
	GetRoles = "https://apis.roblox.com/cloud/v2/groups/%s/roles?maxPageSize=20",
	GetUserMembership = "https://apis.roblox.com/cloud/v2/groups/%s/memberships?filter=user=='users/%s'"
}

local Ranker = {}

local function GetRoles(GroupId : string | number)
	local Url = string.format(ApiUrls.GetRoles, GroupId)

	local Headers = {
		["x-api-key"] = HttpService:GetSecret(SecretName)
	}

	local Roles = {}
	local NextPageToken = nil

	repeat
		local FinalUrl = Url
		if NextPageToken then
			FinalUrl ..= "&pageToken=" .. NextPageToken
		end

		local Success, Response = pcall(function()
			return HttpService:RequestAsync({
				Url = FinalUrl,
				Method = "GET",
				Headers = Headers
			})
		end)

		if not Success or not Response.Success then
			warn("[AutoRanker] Failed to fetch roles:", Response)
			return nil
		end

		local Data = HttpService:JSONDecode(Response.Body)

		for _, Role in Data.groupRoles do
			table.insert(Roles, Role)
		end

		NextPageToken = Data.nextPageToken
	until NextPageToken == ""

	table.sort(Roles, function(a, b)
		return a.rank < b.rank
	end)

	return Roles
end

local function GetRoleId(GroupId : string | number, Rank : number)
	local Roles = GetRoles(GroupId)
	if not Roles then return nil end

	for _, Role in Roles do
		if Role.rank == Rank then
			return Role.id
		end
	end

	return nil
end

local function GetRoleByName(GroupId : string | number, RoleName : string)
	local Roles = GetRoles(GroupId)
	if not Roles then return nil end

	for _, Role in Roles do
		if string.lower(Role.displayName) == string.lower(RoleName) then
			return Role
		end
	end

	return nil
end

local function GetMembershipInfo(GroupId : string | number, UserId : string | number)
	local Url = string.format(ApiUrls.GetUserMembership, GroupId, UserId)

	local Headers = {
		["x-api-key"] = HttpService:GetSecret(SecretName)
	}

	local Success, Response = pcall(function()
		return HttpService:RequestAsync({
			Url = Url,
			Method = "GET",
			Headers = Headers
		})
	end)

	if not Success or not Response.Success then
		warn("[AutoRanker] Failed to fetch membership:", Response)
		return nil
	end

	return HttpService:JSONDecode(Response.Body)
end

local function GetUserRole(GroupId, UserId)
	local Data = GetMembershipInfo(GroupId, UserId)
	if not Data or not Data.groupMemberships or not Data.groupMemberships[1] then
		return nil
	end

	local RolePath = Data.groupMemberships[1].role
	local RoleId = string.match(RolePath, "roles/(%d+)")

	local Roles = GetRoles(GroupId)
	if not Roles then return nil end

	for _, Role in Roles do
		if tostring(Role.id) == RoleId then
			return Role
		end
	end

	return nil
end

function Ranker.RankUser(UserId : number, TargetRank : number, GroupId : number?)
	GroupId = GroupId or DefaultGroupId

	local PathData = GetMembershipInfo(GroupId,UserId)

	if not PathData or not PathData.groupMemberships or not PathData.groupMemberships[1] then
		warn("[AutoRanker] User not found in the group.")
		return false, "User not found in the group."
	end

	local Path = PathData.groupMemberships[1].path
	local RoleId = GetRoleId(GroupId,TargetRank)

	if not RoleId then
		warn("[AutoRanker] Role not found.")
		return false, "Role not found in the group."
	end

	local Url = string.format("https://apis.roblox.com/cloud/v2/%s", Path)

	local Body = {
		path = Path,
		role = string.format("groups/%s/roles/%s",GroupId, RoleId),
		user = string.format("users/%s",UserId)
	}

	local Headers = {
		["Content-Type"] = "application/json",
		["x-api-key"] = HttpService:GetSecret(SecretName)
	}

	local Success, Response = pcall(function()
		return HttpService:RequestAsync({
			Url = Url,
			Method = "PATCH",
			Headers = Headers,
			Body = HttpService:JSONEncode(Body)
		})
	end)

	if not Success then
		warn("[AutoRanker] Request failed:", Response)
		return false, Response.Body or Response
	end

	if Response.Success then
		return true, Response.Body
	else
		warn("[AutoRanker] API Error:", Response.StatusCode, Response.Body)
		return false, Response.Body or Response
	end
end

function Ranker.RankUserByRoleName(UserId : number, RoleName : string, GroupId : number?)
	GroupId = GroupId or DefaultGroupId

	local Role = GetRoleByName(GroupId, RoleName)
	if not Role then
		return false, "Role not found"
	end

	return Ranker.RankUser(UserId, Role.rank, GroupId)
end

function Ranker.Promote(UserId : number, GroupId : number?)
	GroupId = GroupId or DefaultGroupId

	local Roles = GetRoles(GroupId)
	if not Roles then
		return false, "Failed to fetch roles"
	end

	local CurrentRole = GetUserRole(GroupId, UserId)
	if not CurrentRole then
		return false, "User role not found"
	end

	for i, Role in ipairs(Roles) do
		if Role.id == CurrentRole.id then
			local NextRole = Roles[i + 1]
			if not NextRole then
				return false, "Already at highest rank"
			end

			return Ranker.RankUser(UserId, NextRole.rank, GroupId)
		end
	end

	return false, "Role not found in role list"
end

function Ranker.Demote(UserId : number, GroupId : number?)
	GroupId = GroupId or DefaultGroupId

	local Roles = GetRoles(GroupId)
	if not Roles then
		return false, "Failed to fetch roles"
	end

	local CurrentRole = GetUserRole(GroupId, UserId)
	if not CurrentRole then
		return false, "User role not found"
	end

	for i, Role in ipairs(Roles) do
		if Role.id == CurrentRole.id then
			local PrevRole = Roles[i - 1]
			if not PrevRole then
				return false, "Already at lowest rank"
			end

			return Ranker.RankUser(UserId, PrevRole.rank, GroupId)
		end
	end

	return false, "Role not found in role list"
end

return Ranker
Example Code
local Ranker = require(path.to.module)

local success, result = Ranker.RankUserByRoleName(1027658271, "Admin", 12345678)
if success == true then
	print("Sucessfully ranked!")
else
	print("Ranking failed!" , result)
end

Do you find this module useful for your use case?

  • Yes
  • No
0 voters
3 Likes

FINALLY!
I have waited SO long for something like this to become easily accessible to all Players.

THANK YOU!

1 Like