Tutorial · Gameplay

How to make admin commands in Roblox Studio

Admin commands let you run your own game from the chat box: kick a troublemaker, teleport to a friend, test a speed. Roblox's current chat, TextChatService, has commands built in through TextChatCommand. This guide adds a set of admin commands with it, all checked on the server against a list of user IDs, so nobody else can use them.

Updated 23 October 20266 min readIntermediateLuau

In short

In a Script in ServerScriptService, create a TextChatCommand for each command, set its PrimaryAlias (such as "/kick") and parent it to TextChatService. Its Triggered event gives you the sender's TextSource and the full message. Look up the player with Players:GetPlayerByUserId(textSource.UserId), check their user ID is in your admin list, then split the message into words and act.

  • Check the admin list on the server. A check in a LocalScript can be skipped by an exploiter.
  • Check user IDs, not names: players can change their names, IDs never change.
  • A message that matches a command is not shown in chat, so commands do not spam other players.

Get the script

Admin commands script for TextChatService

One Script in ServerScriptService adds /kick, /tp, /speed, /heal and /kill chat commands that only the user IDs in your admin list can use.

Script in ServerScriptService named AdminCommands
-- Script in ServerScriptService named AdminCommands.
-- Chat commands for the user IDs in ADMINS: /kick, /tp, /speed, /heal, /kill.
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local TextChatService = game:GetService("TextChatService")

-- Your user ID is the number in your profile link: roblox.com/users/123456/profile
local ADMINS: { [number]: boolean } = {
	[123456] = true,
}

local function isAdmin(userId: number): boolean
	-- In a Studio playtest of a place you own yourself, you count as an admin too
	local ownerInStudio = RunService:IsStudio() and game.CreatorType == Enum.CreatorType.User and userId == game.CreatorId
	return ADMINS[userId] == true or ownerInStudio
end

-- Finds players by the start of their name or display name, or "me", "all", "others"
local function findPlayers(text: string?, caller: Player): { Player }
	local found: { Player } = {}
	if not text or text == "" or text == "me" then
		table.insert(found, caller)
		return found
	end
	local lower = string.lower(text)
	for _, player in Players:GetPlayers() do
		local name = string.lower(player.Name)
		local display = string.lower(player.DisplayName)
		if lower == "all"
			or (lower == "others" and player ~= caller)
			or string.sub(name, 1, #lower) == lower
			or string.sub(display, 1, #lower) == lower
		then
			table.insert(found, player)
		end
	end
	return found
end

local function getHumanoid(player: Player): Humanoid?
	local character = player.Character
	return character and character:FindFirstChildOfClass("Humanoid")
end

type Handler = (caller: Player, args: { string }) -> ()

local commands: { [string]: Handler } = {
	kick = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			if target ~= caller then
				target:Kick("Kicked by an admin")
			end
		end
	end,
	tp = function(caller, args)
		-- /tp name: go to them. /tp name1 name2: send name1 to name2.
		local destination = if args[3] then findPlayers(args[3], caller)[1] else findPlayers(args[2], caller)[1]
		local movers: { Player } = if args[3] then findPlayers(args[2], caller) else { caller }
		local destCharacter = destination and destination.Character
		if not destCharacter then
			return
		end
		for _, mover in movers do
			local character = mover.Character
			if character and mover ~= destination then
				character:PivotTo(destCharacter:GetPivot() * CFrame.new(0, 0, 4))
			end
		end
	end,
	speed = function(caller, args)
		-- /speed name 50
		local speed = tonumber(args[3]) or 16
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.WalkSpeed = math.clamp(speed, 0, 200)
			end
		end
	end,
	heal = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.Health = humanoid.MaxHealth
			end
		end
	end,
	kill = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.Health = 0
			end
		end
	end,
}

-- Creates one TextChatCommand per command. Only admins get a result; for everyone
-- else the command does nothing, and the message is not shown in chat either.
for name, handler in commands do
	local command = Instance.new("TextChatCommand")
	command.Name = "Admin_" .. name
	command.PrimaryAlias = "/" .. name
	command.AutocompleteVisible = false -- keep the commands out of everyone's suggestions
	command.Parent = TextChatService
	command.Triggered:Connect(function(textSource: TextSource, unfilteredText: string)
		local caller = Players:GetPlayerByUserId(textSource.UserId)
		if not caller or not isAdmin(caller.UserId) then
			return
		end
		handler(caller, string.split(unfilteredText, " "))
	end)
end

RoCode builds it in the place you have open in Roblox Studio, on Windows or macOS with a desktop browser; on a phone the button sends the prompt and the link to your computer. Free to start, with free credits every day. More Roblox Studio scripts

Where to put it

Explorer: one Script, the commands are created in code
ServerScriptService
  AdminCommands Script new

Settings you can change

ADMINSIn the script
The user IDs that can use the commands. Replace 123456 with your own ID, and add a line for each admin. Default: [123456] = true.

Your user ID is the number in your Roblox profile link, for example roblox.com/users/123456/profile. In a Studio playtest of a place you own yourself, you count as an admin even before you add it.

What a cheater can and cannot doEvery command checks the sender's user ID on the server, so a player who is not in ADMINS gets nothing, even with an exploit. Keep the list short: an admin can kick anyone and change anyone's speed.

Type-checked with luau-lsp strict against Roblox's API definitions on 23 October 2026. Not playtested in Studio yet: press Play and watch the Output window before you publish.

Prompt for RoCode

Add admin commands to my game with TextChatService: a Script in ServerScriptService that creates TextChatCommands for /kick, /tp, /speed, /heal and /kill, checks the sender's user ID against an ADMINS list on the server, and accepts a player name, me, all or others as the target.

On this page
  1. Get the script
  2. How it works
  3. 1. The script
  4. 2. Your own command
  5. Common errors
  6. With RoCode
  7. Questions
  8. Sources

How chat commands work

A TextChatCommand inside TextChatService watches messages sent in the chat. When a message starts with its PrimaryAlias or SecondaryAlias, followed by a space or nothing, Roblox does not send the message to other players and fires Triggered instead. The alias match ignores capital letters. Roblox's custom commands example connects to Triggered in a server Script, which is where an admin check belongs.

The commands in this guide
Type in chatWhat happens
/kick NameRemoves that player from the server.
/tp NameTeleports you to them. /tp Name1 Name2 sends Name1 to Name2.
/speed Name 50Sets their WalkSpeed (0 to 200). No number puts it back to 16.
/heal NameSets their health to full.
/kill NameKills their character.

Instead of a name you can write me, all or others, and the start of a name is enough: /heal ro heals every player whose name or display name starts with "ro".

Step 1: Add the commands

  1. Check your game uses TextChatService: select TextChatService in the Explorer and look at ChatVersion in the Properties window. If an old place still says LegacyChatService, Roblox's in-experience text chat page has a section on moving over.
  2. Insert a Script in ServerScriptService named AdminCommands and paste the code below.
  3. Replace 123456 in ADMINS with your user ID.
Script in ServerScriptService named AdminCommands
-- Script in ServerScriptService named AdminCommands.
-- Chat commands for the user IDs in ADMINS: /kick, /tp, /speed, /heal, /kill.
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local TextChatService = game:GetService("TextChatService")

-- Your user ID is the number in your profile link: roblox.com/users/123456/profile
local ADMINS: { [number]: boolean } = {
	[123456] = true,
}

local function isAdmin(userId: number): boolean
	-- In a Studio playtest of a place you own yourself, you count as an admin too
	local ownerInStudio = RunService:IsStudio() and game.CreatorType == Enum.CreatorType.User and userId == game.CreatorId
	return ADMINS[userId] == true or ownerInStudio
end

-- Finds players by the start of their name or display name, or "me", "all", "others"
local function findPlayers(text: string?, caller: Player): { Player }
	local found: { Player } = {}
	if not text or text == "" or text == "me" then
		table.insert(found, caller)
		return found
	end
	local lower = string.lower(text)
	for _, player in Players:GetPlayers() do
		local name = string.lower(player.Name)
		local display = string.lower(player.DisplayName)
		if lower == "all"
			or (lower == "others" and player ~= caller)
			or string.sub(name, 1, #lower) == lower
			or string.sub(display, 1, #lower) == lower
		then
			table.insert(found, player)
		end
	end
	return found
end

local function getHumanoid(player: Player): Humanoid?
	local character = player.Character
	return character and character:FindFirstChildOfClass("Humanoid")
end

type Handler = (caller: Player, args: { string }) -> ()

local commands: { [string]: Handler } = {
	kick = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			if target ~= caller then
				target:Kick("Kicked by an admin")
			end
		end
	end,
	tp = function(caller, args)
		-- /tp name: go to them. /tp name1 name2: send name1 to name2.
		local destination = if args[3] then findPlayers(args[3], caller)[1] else findPlayers(args[2], caller)[1]
		local movers: { Player } = if args[3] then findPlayers(args[2], caller) else { caller }
		local destCharacter = destination and destination.Character
		if not destCharacter then
			return
		end
		for _, mover in movers do
			local character = mover.Character
			if character and mover ~= destination then
				character:PivotTo(destCharacter:GetPivot() * CFrame.new(0, 0, 4))
			end
		end
	end,
	speed = function(caller, args)
		-- /speed name 50
		local speed = tonumber(args[3]) or 16
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.WalkSpeed = math.clamp(speed, 0, 200)
			end
		end
	end,
	heal = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.Health = humanoid.MaxHealth
			end
		end
	end,
	kill = function(caller, args)
		for _, target in findPlayers(args[2], caller) do
			local humanoid = getHumanoid(target)
			if humanoid then
				humanoid.Health = 0
			end
		end
	end,
}

-- Creates one TextChatCommand per command. Only admins get a result; for everyone
-- else the command does nothing, and the message is not shown in chat either.
for name, handler in commands do
	local command = Instance.new("TextChatCommand")
	command.Name = "Admin_" .. name
	command.PrimaryAlias = "/" .. name
	command.AutocompleteVisible = false -- keep the commands out of everyone's suggestions
	command.Parent = TextChatService
	command.Triggered:Connect(function(textSource: TextSource, unfilteredText: string)
		local caller = Players:GetPlayerByUserId(textSource.UserId)
		if not caller or not isAdmin(caller.UserId) then
			return
		end
		handler(caller, string.split(unfilteredText, " "))
	end)
end

The commands table maps each name to a function. The loop at the bottom turns every entry into a TextChatCommand, so adding a command is one more entry in the table. string.split(unfilteredText, " ") breaks the message into words: args[1] is the command, args[2] the target, args[3] a number or a second target.

AutocompleteVisible = false keeps the commands out of the suggestions every player sees when they type a slash. They still work when an admin types them in full.

Step 2: Add your own command

Add an entry to the commands table. For example, a /jump Name 100 command that sets jump power:

  • Copy the speed entry and rename it jump.
  • Inside it, set humanoid.UseJumpPower = true and humanoid.JumpPower = math.clamp(power, 0, 300).

For a message to everyone, such as an announcement, send it through a RemoteEvent that every client shows on screen. Anything players can read that came from typed text has to go through Roblox's text filtering first, so keep announcements to fixed messages, or filter them with TextService:FilterStringAsync.

Test it

  1. Press Play in a place you own. Type /speed me 60 in chat: you run faster, and the message does not appear in chat.
  2. Type /heal me after falling to check health returns.
  3. Start a 2-player test (Test tab, Clients and Servers). From Player1 type /tp Player2. From Player2, which is not an admin, type /kill Player1: nothing happens.

Common errors and fixes

If a command does nothing, check the Output window and these first.

Typing the command shows it in chat and nothing happens

TextChatCommand belongs to TextChatService. Check TextChatService's ChatVersion property, and that the script is a Script in ServerScriptService with no red error in the Output.

The command does nothing for me in the live game

Your user ID is not in ADMINS. The Studio exception only applies in Studio and only for a place owned by your own account, not a group.

/tp does nothing

Both players need a character in the world. Check the target name: the start of the name or display name is enough, but a typo matches nobody.

I want ranks such as moderator and owner

Change ADMINS to map user IDs to a rank number, and give each command the lowest rank allowed to run it.

Build it with RoCode

If you would rather not set this up by hand, RoCode can build it in the place you have open. RoCode is an independent AI agent for Roblox Studio, not made by Roblox: you describe the change in a web chat, and its Studio plugin creates the scripts in your Explorer.

You type

Add admin commands for me and my friend (user IDs 111 and 222): /kick, /tp, /speed, /heal, /kill and /fly, checked on the server.

RoCode does
  • Writes the command script with your admin IDs and any extra commands you describe
  • Can read your place first to reuse what is there, such as an existing chat setup
  • Is told to compile-check each script with Check Script, and sets undo points around each batch

You still test the commands in a playtest, and with a second player for the admin check. How RoCode connects to Studio.

Questions

How do I add admin commands to my Roblox game?

Create TextChatCommands in a server Script, and in each Triggered handler check the sender's user ID against your admin list before acting. Step 1 has the full script.

Do I need HD Admin or Adonis?

No. Admin systems from the Creator Store add many commands and a panel, but they are large code you did not write. A short script of your own does exactly what you need and nothing else.

How do I find my Roblox user ID?

Open your profile on roblox.com: the number in the link, between /users/ and /profile, is your user ID.

Can players see admin commands in chat?

No. A message that matches a TextChatCommand is not sent to other players.

Sources and further reading

How the code was checked: every script on this page passes Luau's strict type checker against Roblox's API definitions (luau-lsp, 23 October 2026). A type check catches misspelt APIs and wrong types, not game logic, so playtest in Studio before you publish.

Try RoCode on your own place.

Free every day, no card required. Install the plugin, describe the feature, and review what lands in Studio.