Building Custom Admin GUIs
TutorialStep-by-step implementation guide for building custom in-game admin panels using Native ScreenGui, Roact, or Chat Commands.
# Overview
Because R Anti-Cheat provides a headless ClientAPI, you have complete freedom to build any in-game admin panel that suits your experience.
Below are three complete, production-ready examples:
- Example 1: Native Roblox
ScreenGuiAdmin Panel (Zero external libraries). - Example 2: Declarative Reactive Component (Roact / React-lua / Fusion).
- Example 3: Custom Chat Commands (
/kick,/ban,/freeze).
# Example 1: Native Roblox ScreenGui Admin Panel
Create a LocalScript inside StarterPlayer.StarterPlayerScripts (e.g. CustomAdminPanel.client.luau):
--!strict
-- StarterPlayerScripts/CustomAdminPanel.client.luau
local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local RACClient = require(ReplicatedStorage.RAC.ClientAPI)
local isOpen = false
local screenGui: ScreenGui? = nil
local function RefreshPlayerList(scrollingFrame: ScrollingFrame)
-- Clear old entries
for _, child in ipairs(scrollingFrame:GetChildren()) do
if child:IsA("Frame") then child:Destroy() end
end
local success, players = RACClient.GetLivePlayers()
if not success or not players then return end
local yOffset = 0
for _, p in ipairs(players) do
local row = Instance.new("Frame")
row.Size = UDim2.new(1, -10, 0, 44)
row.Position = UDim2.new(0, 5, 0, yOffset)
row.BackgroundColor3 = Color3.fromRGB(30, 34, 40)
row.BorderSizePixel = 0
row.Parent = scrollingFrame
local corner = Instance.new("UICorner")
corner.CornerRadius = UDim.new(0, 6)
corner.Parent = row
-- Player info label
local infoLabel = Instance.new("TextLabel")
infoLabel.Size = UDim2.new(0.6, 0, 1, 0)
infoLabel.Position = UDim2.new(0, 10, 0, 0)
infoLabel.BackgroundTransparency = 1
infoLabel.Font = Enum.Font.GothamMedium
infoLabel.TextSize = 13
infoLabel.TextColor3 = Color3.fromRGB(240, 240, 240)
infoLabel.TextXAlignment = Enum.TextXAlignment.Left
infoLabel.Text = string.format("%s (Ping: %dms | Risk: %d)", p.username, p.ping, p.riskScore)
infoLabel.Parent = row
-- Risk Color Indicator
if p.riskScore >= 70 then
infoLabel.TextColor3 = Color3.fromRGB(239, 68, 68) -- Critical Red
elseif p.riskScore >= 35 then
infoLabel.TextColor3 = Color3.fromRGB(245, 158, 11) -- Warning Amber
end
-- Kick Button
local kickBtn = Instance.new("TextButton")
kickBtn.Size = UDim2.new(0, 60, 0, 28)
kickBtn.Position = UDim2.new(1, -135, 0.5, -14)
kickBtn.BackgroundColor3 = Color3.fromRGB(220, 38, 38)
kickBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
kickBtn.Font = Enum.Font.GothamBold
kickBtn.TextSize = 11
kickBtn.Text = "KICK"
kickBtn.Parent = row
Instance.new("UICorner", kickBtn).CornerRadius = UDim.new(0, 4)
kickBtn.MouseButton1Click:Connect(function()
local ok, err = RACClient.PunishPlayer("Kick", p.userId, p.username, "Kicked by Administrator")
if ok then
row:Destroy()
else
warn("Failed to kick:", err)
end
end)
-- Ban Button
local banBtn = Instance.new("TextButton")
banBtn.Size = UDim2.new(0, 60, 0, 28)
banBtn.Position = UDim2.new(1, -70, 0.5, -14)
banBtn.BackgroundColor3 = Color3.fromRGB(153, 27, 27)
banBtn.TextColor3 = Color3.fromRGB(255, 255, 255)
banBtn.Font = Enum.Font.GothamBold
banBtn.TextSize = 11
banBtn.Text = "BAN"
banBtn.Parent = row
Instance.new("UICorner", banBtn).CornerRadius = UDim.new(0, 4)
banBtn.MouseButton1Click:Connect(function()
RACClient.PunishPlayer("PermBan", p.userId, p.username, "Banned by Administrator")
row:Destroy()
end)
yOffset += 48
end
scrollingFrame.CanvasSize = UDim2.new(0, 0, 0, yOffset + 10)
end
local function OpenAdminPanel()
local ok, stats, role = RACClient.GetOverviewData()
if not ok then
warn("[CustomAdmin] Access Denied: You are not authorized to open the Admin Panel.")
return
end
isOpen = true
screenGui = Instance.new("ScreenGui")
screenGui.Name = "CustomRACAdminGui"
screenGui.ResetOnSpawn = false
screenGui.Parent = playerGui
-- Main Container Window
local mainFrame = Instance.new("Frame")
mainFrame.Size = UDim2.new(0, 520, 0, 380)
mainFrame.Position = UDim2.new(0.5, -260, 0.5, -190)
mainFrame.BackgroundColor3 = Color3.fromRGB(20, 23, 28)
mainFrame.BorderSizePixel = 0
mainFrame.Parent = screenGui
Instance.new("UICorner", mainFrame).CornerRadius = UDim.new(0, 10)
-- Header Title
local title = Instance.new("TextLabel")
title.Size = UDim2.new(1, -40, 0, 36)
title.Position = UDim2.new(0, 16, 0, 8)
title.BackgroundTransparency = 1
title.Font = Enum.Font.GothamBold
title.TextSize = 16
title.TextColor3 = Color3.fromRGB(255, 255, 255)
title.TextXAlignment = Enum.TextXAlignment.Left
title.Text = string.format("🛡️ Security Admin Panel — Role: %s", role or "Admin")
title.Parent = mainFrame
-- Scrolling Player List
local scroll = Instance.new("ScrollingFrame")
scroll.Size = UDim2.new(1, -32, 1, -60)
scroll.Position = UDim2.new(0, 16, 0, 48)
scroll.BackgroundTransparency = 1
scroll.BorderSizePixel = 0
scroll.ScrollBarThickness = 4
scroll.Parent = mainFrame
RefreshPlayerList(scroll)
end
local function CloseAdminPanel()
isOpen = false
if screenGui then
screenGui:Destroy()
screenGui = nil
end
end
-- Keybind toggle: F4 key
UserInputService.InputBegan:Connect(function(input, gpe)
if gpe then return end
if input.KeyCode == Enum.KeyCode.F4 then
if isOpen then CloseAdminPanel() else OpenAdminPanel() end
end
end)
# Example 2: In-Game Chat Moderation Commands
You can connect RAC directly to Roblox's modern TextChatService:
-- ServerScriptService/ChatModerator.luau
local TextChatService = game:GetService("TextChatService")
local Players = game:GetService("Players")
local RAC = require(game.ServerScriptService.RAC.Core.Engine)
local PermissionManager = RAC:GetPermissionManager()
local kickCommand = Instance.new("TextChatCommand")
kickCommand.Name = "RACKickCommand"
kickCommand.PrimaryAlias = "/kick"
kickCommand.Parent = TextChatService:WaitForChild("TextChatCommands")
kickCommand.Triggered:Connect(function(originTextSource, message)
local sender = Players:GetPlayerByUserId(originTextSource.UserId)
if not sender or not PermissionManager.CanExecute(sender, "KICK_PLAYER") then
return
end
local tokens = string.split(message, " ")
local targetName = tokens[2]
local reason = table.concat(tokens, " ", 3) or "Kicked by staff"
local targetPlayer = Players:FindFirstChild(targetName) :: Player?
if targetPlayer then
RAC:KickPlayer(targetPlayer, reason, sender.Name)
end
end)
# Next Steps
- Review the complete API parameters in Client API & Remotes Reference.
- Learn about Role-Based Access Control.