Skip to main content
This feature requires:

Panora Basic Admin Essentials Demote Integration

πŸ“₯ Installation

1

Open Roblox Studio

Navigate to your Basic Admin Essentials model in Roblox Studio.
2

Inserting Script

Create a Module Script under the Plugins folder, get rid of everything in it, and paste the following.
PanoraDemote.lua
--[[
───────────────────────────────────────────────────────────────
πŸ“¦ Basic Admin Essentials Plugin β€” Demote Command
───────────────────────────────────────────────────────────────
Developed and maintained by: Panora Connect LLC
Website: https://panora.cc

This module integrates Panora Connect’s API with Basic Admin Essentials,
allowing workspace administrators to demote users directly in-game using
the official Panora ranking system.

───────────────────────────────────────────────────────────────
βš™οΈ Configuration Guide
───────────────────────────────────────────────────────────────
The configuration is stored at the top of this script.
You can set the following variables:

β€’ API_KEY  β†’ Your Panora API key (from your workspace)
β€’ GROUP_ID β†’ Your Roblox group ID
β€’ COOLDOWN_TIME β†’ Optional; adds cooldown between demotions
β€’ WEBHOOK_URL β†’ Optional; External webhook for demotion logs
β€’ MIN_USERNAME_LENGTH β†’ Optional; minimum username length to demote
β€’ EveryoneAntiAbuse β†’ Optional; prevents non-Everyone rank demotion

Once configured, simply place this plugin inside your Basic Admin
β€œPlugins” folder. No other dependencies need to be modified.

───────────────────────────────────────────────────────────────
πŸ’¬ Support & Contact
───────────────────────────────────────────────────────────────
For assistance, please reach out through one of the following:

πŸ“§ Email: support@panora.cc  
🌐 Live Chat: https://app.panora.cc  
πŸ’¬ Community Discord: https://discord.gg/panora  

───────────────────────────────────────────────────────────────
(c) 2025 Panora Connect LLC. All rights reserved.
Unauthorized redistribution or sale of this module is prohibited.
───────────────────────────────────────────────────────────────
]]

local HttpService = game:GetService("HttpService")
local GroupServiceModule = require(95677908346714)
local PanoraAPI = require(111358642560007)

local Plugin = function(...)
local Data = {...}
local remoteEvent = Data[1][1]
local remoteFunction = Data[1][2]
local returnPermissions = Data[1][3]
local Commands = Data[1][4]
local Prefix = Data[1][5]
local actionPrefix = Data[1][6]
local returnPlayers = Data[1][7]
local cleanData = Data[1][8]
local pluginName = script.Name
local pluginPrefix = Prefix
local pluginLevel = 2
local pluginUsage = "<username>"
local pluginDescription = "Demote a user | Panora"
local BASE_URL = "https://api.panora.cc/v1/ranker"




local API_KEY = "YOUR_PANORA_API_KEY"
local GROUP_ID = 000000
local COOLDOWN_TIME = 0
local WEBHOOK_URL = ""
local MIN_USERNAME_LENGTH = 2
local EveryoneAntiAbuse = true


--─────────────────────────────────────────────────────────────────────────────────────────────────────────
-- ⚠️ DO NOT EDIT PAST THIS LINE UNLESS YOU KNOW WHAT YOU ARE DOING. WE DO NOT ASSIST WITH MODIFIED CODES!!
--─────────────────────────────────────────────────────────────────────────────────────────────────────────

local recentDemotions = {}

local function log(tag, ...)
	print(string.format("[%s]", tag), ...)
end

local function findPreviousRank(currentRank)
	local roles = GroupServiceModule.getGroupRoles(GROUP_ID)
	table.sort(roles, function(a, b)
		return a.Rank < b.Rank
	end)
	for i = #roles, 1, -1 do
		if roles[i].Rank < currentRank then
			return roles[i]
		end
	end
	return nil
end

local function processDemotion(player, target)
	log("DEMOTE", "Processing", target.Name)

	if player.UserId == target.UserId then
		log("DEBUG", "Skipped self-demotion for", target.Name)
		return string.format("%s - Skipped (Cannot demote self)", target.Name)
	end

	local last = recentDemotions[target.UserId]
	if last and os.time() - last < COOLDOWN_TIME then
		local remaining = COOLDOWN_TIME - (os.time() - last)
		log("DEBUG", string.format("Cooldown active for %s (%ds left)", target.Name, remaining))
		return string.format("%s - Cooldown active (%ds left)", target.Name, remaining)
	end

	local currentRank = GroupServiceModule.getUserGroupRank(target.UserId, GROUP_ID)
	local currentRoleName
	for _, role in pairs(GroupServiceModule.getGroupRoles(GROUP_ID)) do
		if role.Rank == currentRank then
			currentRoleName = role.Name
			break
		end
	end

	local prevRank = findPreviousRank(currentRank)
	if not prevRank then
		log("DEBUG", string.format("%s is already at lowest rank", target.Name))
		return string.format("%s - Already at lowest rank", target.Name)
	end

	local rankData = {
		rankerName = player.Name,
		rankerId = player.UserId,
		rankeeName = target.Name,
		rankeeId = target.UserId,
		newRankId = prevRank.Rank,
		oldRankName = currentRoleName,
		command = "demote | BAE Integration",
		prefix = Prefix,
		webhookUrl = WEBHOOK_URL
	}

	local success, message = PanoraAPI.rankUser(BASE_URL, API_KEY, rankData)

	if success then
		recentDemotions[target.UserId] = os.time()
		log("PANORA", string.format("Demotion succeeded for %s (%s)", target.Name, message))
		return string.format("%s - Success", target.Name)
	else
		log("PANORA", string.format("Demotion failed for %s (%s)", target.Name, message))
		return string.format("%s - Failed (%s)", target.Name, message or "Unknown error")
	end
end

local function processMultiple(player, targets)
	local results = {}
	for _, t in pairs(targets) do
		local result = processDemotion(player, t)
		table.insert(results, result)
	end

	local summary = table.concat(results, "\n")
	remoteEvent:FireClient(player, summary)
	return summary
end

local function pluginFunction(Args)
	local player = Args[1]
	local target = Args[3]

	if not target then
		return "Please provide a username or team prefix."
	end

	local lowered = string.lower(target)
	if EveryoneAntiAbuse and (lowered == "everyone" or lowered == "all") then
		return "Anti-Abuse setting is enabled. Mass demotions are not allowed."
	end

	if #target < MIN_USERNAME_LENGTH then
		return string.format("Username must be at least %d characters long.", MIN_USERNAME_LENGTH)
	end

	if target:sub(1, 1) == "%" then
		local teamName = target:sub(2)
		local team = game.Teams:FindFirstChild(teamName)
		local victims = {}

		if not team then
			for _, t in pairs(game.Teams:GetChildren()) do
				if string.sub(t.Name, 1, 1):lower() == string.sub(teamName, 1, 1):lower() then
					team = t
					break
				end
			end
		end

		if team then
			for _, plr in pairs(game.Players:GetPlayers()) do
				if plr.Team == team then
					table.insert(victims, plr)
				end
			end
		end

		if #victims == 0 then
			return "No players found in the specified team."
		end

		log("DEMOTE", string.format("Team demotion started (%s) - %d users", team.Name, #victims))
		local summary = processMultiple(player, victims)
		return summary
	end

	local victims = returnPlayers(player, target)
	if not victims or #victims == 0 then
		return "Couldn't find player."
	end

	local summary = processMultiple(player, victims)
	return summary
end

local desc = string.format("%s%s %s\n%s", pluginPrefix, pluginName, pluginUsage ~= "" and pluginUsage or "", pluginDescription)
return pluginName, pluginFunction, pluginLevel, pluginPrefix, {pluginName, pluginUsage, pluginDescription}
end

return Plugin
3

API Key

  • Visit https://app.panora.cc and copy your API Key.
    See API Key
  • Replace the API_KEY in the script with your actual API key.
  • Under the Panora Dashboard > Panora API, whitelist your game’s Universal Game ID.
    See Whitelist
4

Roblox Group

  • Set your GROUP_ID to match the Roblox group linked to your Panora workspace.
    See Initial Setup
  • Configure your group ranks in the ranks table in ascending order.
  • Set a cooldown time to avoid rapid promotions (recommended: 20 seconds).

βš™οΈ Configuration

VariableDescriptionRequired
API_KEYYour Panora API key from app.panora.ccβœ…
GROUP_IDRoblox Group ID linked to your workspaceβœ…
ranksAn ordered table of your group ranksβœ…
COOLDOWN_TIMESeconds to wait before promoting a user again⚠️ Recommended
Example ranks table:
local ranks = {
  {rankId = 1, rankName = "Member"},
  {rankId = 2, rankName = "Moderator"},
  {rankId = 3, rankName = "Admin"},
}