A Roblox admin panel is a GUI that sends commands to the server through a RemoteEvent, and the server checks that the sender is an admin on every call before it does anything. Kicks use Player:Kick on the server, and for bans you can use the official Players:BanAsync API or a DataStore ban list that is checked on PlayerAdded. BanAsync is the recommended option because it also covers suspected alt accounts and removes the player from every running server.
How an admin panel works
There are three parts:
- The GUI (client). A ScreenGui with a player list, buttons like Kick and Ban, and a reason TextBox.
- **The RemoteEvent.** One RemoteEvent in ReplicatedStorage, for example
AdminCommand, that carries the command from the client to the server. - The server Script. A Script in ServerScriptService that checks if the sender is an admin, validates the arguments and runs the command.
The server is the source of truth. It should verify what the client claims before acting on it, as the Roblox remote events docs explain. A LocalScript admin check is easy for exploiters to bypass, so hiding the panel from non-admins is only cosmetic. The real check has to happen on the server when the RemoteEvent fires.
Step by step: build your own admin panel
1. List your admins by UserId
Use UserIds, not usernames, because usernames can change. Your UserId is the number in your profile URL, for example roblox.com/users/123456789/profile.
You can also allow anyone at or above a group rank. On the Player object, both GetRankInGroup and GetRankInGroupAsync are now deprecated. Use [GroupService:GetRolesInGroupAsync(userId, groupId)](https://create.roblox.com/docs/reference/engine/classes/GroupService) instead. It returns a table with IsMember and Roles. Roles is sorted from highest to lowest, and each role has Id, Name and Rank. Results are cached and may be out of date, so a rank change may not show up right away. The docs sample for this method runs on the client, so test it in your server Script before you publish.
2. Create the RemoteEvent
In ReplicatedStorage, insert a RemoteEvent and name it AdminCommand.
3. Build the GUI and LocalScript
Put the ScreenGui in **ServerStorage**, not StarterGui, and name it AdminPanel. Anything in StarterGui is copied to every player, so everyone would see your panel. The server Script in step 4 gives a copy only to admins.
Inside AdminPanel, add a Frame with:
- a ScrollingFrame named
PlayerListwith a UIListLayout inside it - a TextBox named
ReasonBox - TextButtons named
KickButtonandBanButton
Then add a LocalScript inside the ScreenGui:
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local AdminCommand = ReplicatedStorage:WaitForChild("AdminCommand")
local frame = script.Parent:WaitForChild("Frame")
local playerList = frame:WaitForChild("PlayerList")
local reasonBox = frame:WaitForChild("ReasonBox")
local kickButton = frame:WaitForChild("KickButton")
local banButton = frame:WaitForChild("BanButton")
local selectedUserId = nil
local function refreshList(leaving)
for _, child in playerList:GetChildren() do
if child:IsA("TextButton") then
child:Destroy()
end
end
for _, plr in Players:GetPlayers() do
if plr ~= leaving then
local button = Instance.new("TextButton")
button.Size = UDim2.new(1, 0, 0, 30)
button.Text = plr.Name
button.Parent = playerList
button.Activated:Connect(function()
selectedUserId = plr.UserId
end)
end
end
end
local function send(command)
if selectedUserId then
AdminCommand:FireServer(command, selectedUserId, reasonBox.Text)
end
end
kickButton.Activated:Connect(function() send("Kick") end)
banButton.Activated:Connect(function() send("Ban") end)
Players.PlayerAdded:Connect(function() refreshList() end)
Players.PlayerRemoving:Connect(function(plr)
if plr.UserId == selectedUserId then
selectedUserId = nil
end
refreshList(plr)
end)
refreshList()Do not pass the local player to FireServer. A common bug is adding the player as an extra argument, which shifts every other argument by one. The server already receives the firing player as the first parameter.
4. Write the server Script
Put this Script in ServerScriptService. It hands the panel to admins, then checks admin status on every command and validates every argument.
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local GroupService = game:GetService("GroupService")
local AdminCommand = ReplicatedStorage:WaitForChild("AdminCommand")
local panelTemplate = ServerStorage:WaitForChild("AdminPanel")
local ADMIN_IDS = {
[123456789] = true, -- replace with your UserId
}
local ADMIN_GROUP_ID = 0 -- set to your group ID to allow group ranks
local ADMIN_MIN_RANK = 250
local function isAdmin(player)
if ADMIN_IDS[player.UserId] then
return true
end
if ADMIN_GROUP_ID ~= 0 then
local ok, result = pcall(function()
return GroupService:GetRolesInGroupAsync(player.UserId, ADMIN_GROUP_ID)
end)
return ok and result.IsMember and result.Roles[1] ~= nil
and result.Roles[1].Rank >= ADMIN_MIN_RANK
end
return false
end
local commands = {}
commands.Kick = function(admin, target, reason)
target:Kick(reason)
end
commands.Ban = function(admin, target, reason)
local ok, err = pcall(function()
Players:BanAsync({
UserIds = {target.UserId},
Duration = 86400, -- 24 hours, or -1 for permanent
DisplayReason = reason,
PrivateReason = "Banned by " .. admin.Name .. " (" .. admin.UserId .. ")",
})
end)
if not ok then
warn("Ban failed:", err)
end
end
commands.Bring = function(admin, target)
local adminRoot = admin.Character and admin.Character:FindFirstChild("HumanoidRootPart")
local targetCharacter = target.Character
if adminRoot and targetCharacter then
targetCharacter:PivotTo(adminRoot.CFrame * CFrame.new(0, 0, -4))
end
end
AdminCommand.OnServerEvent:Connect(function(player, command, targetUserId, reason)
if not isAdmin(player) then
warn(player.Name .. " fired AdminCommand without admin access")
return
end
if type(command) ~= "string" or not commands[command] then
return
end
if type(targetUserId) ~= "number" then
return
end
local target = Players:GetPlayerByUserId(targetUserId)
if not target then
return
end
if (command == "Kick" or command == "Ban") and isAdmin(target) then
warn(player.Name .. " tried to " .. command .. " an admin")
return
end
if type(reason) ~= "string" or reason == "" or not utf8.len(reason) then
reason = "No reason given"
end
if #reason > 400 then
reason = string.sub(reason, 1, utf8.offset(reason, 0, 401) - 1)
end
local runCommand = commands[command]
runCommand(player, target, reason)
end)
local function givePanel(player)
if isAdmin(player) then
local panel = panelTemplate:Clone()
panel.ResetOnSpawn = false
panel.Parent = player:WaitForChild("PlayerGui")
end
end
Players.PlayerAdded:Connect(givePanel)
for _, player in Players:GetPlayers() do
task.spawn(givePanel, player)
endA few things to notice:
- The first parameter of
OnServerEventis always the Player who fired it. Roblox fills that in for you, so the server never trusts a player sent inside the payload. - Kick and Ban refuse to target any admin. That stops you from kicking yourself with a misclick, and it stops one admin from banning another admin or the owner.
- The reason is cut to 400 **bytes** on a character boundary.
DisplayReasonallows 400 characters and a kick message allows 500 bytes, so 400 bytes fits both. Emoji and many non-English letters take more than one byte, which is why the code usesutf8.offsetinstead of a plainstring.subthat could cut a character in half. - Giving the panel only to admins is cosmetic. The
isAdmincheck insideOnServerEventis the real protection. - The
commandstable keeps each command in one place. Adding a new one means adding one function. Some developers split each command into its own ModuleScript andrequireit from the same RemoteEvent handler. - This panel only targets players who are in the server right now. BanAsync also accepts UserIds of offline players if you add a UserId TextBox.
5. Add more commands on the server
Freeze, walkspeed and heal work the same way: they run on the server after the admin check. For example, a freeze toggle:
commands.Freeze = function(admin, target)
local root = target.Character and target.Character:FindFirstChild("HumanoidRootPart")
if root then
root.Anchored = not root.Anchored
end
endAdd a FreezeButton and call send("Freeze") from the LocalScript. Fly is the exception: the server only authorizes it, and the client applies the movement.
Banning with BanAsync
[Players:BanAsync](https://create.roblox.com/docs/reference/engine/classes/Players) is the official Ban API. It can only be called from the server, and calling it from the client errors.
Before it works, turn on banning. In Studio, select **Players** in the Explorer and tick **BanningEnabled** in the Properties window. You can't set it from a script. It controls BanAsync, UnbanAsync and GetBanHistoryAsync.
Required fields:
UserIds: an array of UserIds, up to 50 per call. It must be a table, so use{userId}even for one person.Duration: whole seconds as a number. Use-1for permanent.0and other negative numbers are invalid. A string here makes the ban fail.DisplayReason: what the banned user sees. Up to 400 characters, and Roblox text-filters it.PrivateReason: notes for you and your team. Up to 1000 characters, not filtered.
Optional fields:
ApplyToUniverse: defaults totrue, so the ban covers every place in your experience.ExcludeAltAccounts: defaults tofalse, so the ban spreads to suspected alt accounts unless you set it totrue.ApplyDeviceBlock: defaults tofalse. Whentrue, it blocks the device for 24 hours.
BanAsync also removes the player from every running server right away, so you don't need to call Kick as well.
The official code sample wraps the call in pcall, uses a 24 hour duration and suggests building your own logic based on the user's ban history. You can read that history with Players:GetBanHistoryAsync, but it only works on live servers, not in Studio or on the client.
Unbanning
Use UnbanAsync with the same ApplyToUniverse scope as the original ban, or it will not lift it:
local ok, err = pcall(function()
Players:UnbanAsync({
UserIds = {userId},
ApplyToUniverse = true,
})
end)
if not ok then
warn("Unban failed:", err)
endOnly UnbanAsync lifts a device block. Unbanning any other way, such as through Open Cloud or the Creator Hub, does not.
Testing your panel
- **Check the panel as an admin.** Press Play in Studio. You join with your own account, so if your UserId is in
ADMIN_IDS, the panel shows up. - **Check that non-admins are blocked.** In Studio, open the Test tab and start **Clients and Servers** with 2 players. Test players are not on your admin list, so they don't get the panel. In one client window, run
game.ReplicatedStorage.AdminCommand:FireServer("Kick", game.Players.LocalPlayer.UserId)in the command bar. The server output should show the warning, and nobody should be kicked. - Bans in Studio are not real. BanAsync works in Studio and team test, but those bans do not apply to the live game.
- **Publish for the rest.** Real bans and
GetBanHistoryAsynconly work on live servers, so test those in a published game with an alt account as the target.
For more on why the server check matters, read the anti-exploit guide.
Using a ready-made admin system
If you want something that works right away:
- **Adonis** is a community-maintained server moderation system. Insert the official loader model from the Toolbox into ServerScriptService (not Workspace). Configure it in
Adonis_Loader > Config > Settings, and changesettings.DataStoreKeyto a random value first. - HD Admin is another popular option. Older guides describe its classic rank setup, but newer versions changed how ranks work, so follow the setup notes that come with the version you insert.
**Be careful in the Toolbox.** Search for the model from its official creator account and check the creator name before inserting. Fake copies can hide backdoors that give strangers admin in your game. If a model has a script with require(<number>) that you didn't add, delete it.
There are also paid panels on sites like BuiltByBit, itch.io and Gumroad. You can't audit them before you buy, so check reviews and read the scripts after inserting.
Generating an admin panel with Picoo
[Picoo](https://picoo.io/) has a dedicated [admin panel builder](/how-to-make/roblox-admin-panel). Type something like "make me an admin panel with kick and ban" and it creates the server Script (AdminServer in ServerScriptService), the client script (AdminClient) and the AdminPanel GUI for you.
- It has 11 commands: Bring, Go To, Freeze, Heal, God, Speed, Kill, Kick, Ban, Fly (me) and Announce.
- An
isAdmin(plr)check runs on the server before any command. A non-admin who fires the remote gets a warning in the server log and nothing happens. - In a user-owned place the owner is an admin automatically. In a group game, list your admins explicitly, since it uses an explicit list and not group ranks. You can name admins by username or UserId. UserId is the reliable option because usernames change.
- Bans are saved to a DataStore (
PicooAdminBans) by default and checked again on PlayerAdded, so a banned player is kicked when they join. In Studio play tests or an unpublished place they only last for the current server. Session-only bans are also an option. It does not use BanAsync, so if you want the official Ban API you would need to rewrite the ban part ofAdminServeryourself. The DataStore guide explains how saved data works. - The panel opens with F2 by default, and you can pick another letter or F-key. The GUI is built as real instances in Edit mode, so you can restyle it without pressing Play.
It is premium-only and has no free tier. The pricing breakdown covers the Pro and Business plans.
FAQ
Does BanAsync ban alt accounts?
By default, yes. Bans spread to suspected alt accounts unless you set ExcludeAltAccounts = true. One DevForum feature request reports that newly created alts are not caught, but that is a user report, not official documentation.
Is hiding the admin GUI from other players enough?
No. Exploiters can fire your RemoteEvent without ever seeing the GUI. The server has to check isAdmin on every call.
Should I list admins by username or UserId?
UserId. Usernames can change, so a username list can break or give access to the wrong person.
Can I get admin in someone else's game?
No. An admin panel only works in games you build or manage. Scripts that claim to give you admin in other people's games are exploits, and using them breaks Roblox's Terms of Use.