How to create an admin system, in-depth

I’ve updated the code to be efficient, however I don’t have a tutorial made for it but here’s the updated code that I recommend you use.

local Admins = {-1,"Just2Terrify"}
local Prefix,Seperator = "/"," | "

local Commands = {
	["Example Command"] = { --[["Title of the command"]]
		CommandPhrases = {"example1","example2","ex1"}; --[[Add the phrases you want to use to trigger the event.. make sure it is lowercase.. else it won't see the phrase.]]
		Level = 6, --[[This is the level of admin needed to use the command.]]
		Active = true, --[[This determines if the command is able to be used or not.]]
		Description = "This is where the commands description will be.", --[[This is where you describe what the command is supposed to do.]]
		Function = function(Args) --[[This is the function that you need to call to run the command.]]
			warn(Args)
		end}
	;

}


--[[This will put all string values into lowercase, credit to AskForHeaven for the idea.]]
for i, admin in Admins do
	if typeof(admin) ~= "string" then
		continue
	end

	Admins[i] = string.lower(admin)
end

game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		
		if not table.find(Admins,string.lower(plr.Name)) and not table.find(Admins,plr.UserId) then
			return
		end
		
		warn(plr.Name.." has chatted.")
		
		local checkForMultipleCmds = string.split(message," | ") or message
		for Ind,SeparateCommand in pairs(checkForMultipleCmds) do
			local searchForCommand = string.split(SeparateCommand, " ")[1] or SeparateCommand;local commandSent = string.split(searchForCommand,Prefix)[2]
			if commandSent then
				for Command,CommandTable in pairs(Commands) do
					
					if not CommandTable.Active then
						continue
					end
					
					if not table.find(CommandTable.CommandPhrases,string.lower(commandSent)) then
						break
					end
					
					CommandTable.Function(string.split(SeparateCommand,commandSent.." ")[2])
				end
			end
		end
	end)
end)
Original Post

Beginning Note

This is made to help you understand how to create the basics of an Admin System, in the form of chat commands; I made sure to hide the coding portions, in-case you want to figure out how to code it on your own. I used the basics of coding for this, to make it much more understandable.


Essentials

I’m going to list a few things you need to understand before you start creating an admin system;

  1. Understand how the server and client interact with each-other, and seperately. I can’t express how important this is, if I understood how the server and client co-existed… my first attempt at an admin system would of been drastically different. I’ve attached a link that explains this in some depth: click here.

  2. Understand how RemoteEvents, & RemoteFunctions work. These are how you’ll communicate between the server and the client(s) for 99% of your commands. I’d also recommend learning what BindableEvents and BindableFunctions are. They can be handy in certain situations.

  3. Your first try should be a learning experience for you. Your first admin system will not match the same level as some of the popular admin systems, such as Adonis and Kohls. Use it as a step towards a bigger goal.

  4. Have fun with your creation. If you think something may be a little weird to add to an admin system, who cares? Give it a shot, the worst thing that can happen is you remove it later.

  5. Take inspiration from others. They’re other admin systems that may have certain things you may want, don’t be afraid to use them as an inspiration to create your own system.


After you’ve gone through the essentials, you’re probably wondering how to start creating the admin system. All you need, is a script, and a RemoteEvent. Put the RemoteEvent in your ReplicatedStorage, and name it what you desire. Although I do recommend using a module script alongside a regular script… it isn’t necessary.

All of the steps

Step One

For this, I will be using a regular script only.

The first step, is to create a table that will hold your commands. Why do you need a table? This is so you can search through every command, to find which command you’re trying to activate. Here’s how I’d recommend structuring the table:

Scripting Example
local Commands = {
	["Example Command"] = { --[["Title of the command"]]
		CommandPhrases = {"example1","example2","ex1"}; --[[Add the phrases you want to use to trigger the event.]]
		Level = 6, --[[This is the level of admin needed to use the command.]]
		Active = true, --[[This determines if the command is able to be used or not.]]
		Description = "This is where the commands description will be.", --[[This is where you describe what the command is supposed to do.]]
		Function = function(plr) --[[This is the function that you need to call to run the command.]]
			warn("Example Command in FunCommands Ran")
		end}
	;
	
}

Step Two

Create a connection to the player chatting. Here’s how you’d do that;

Scripting Example
game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		warn(message)
	end)
end)

Step Three

Once you’ve been able to fire a message, you then need to create a way to find these commands; how you’d do that is by creating a In Pairs check;

Scripting Example
game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		for Command,CommandTable in pairs(Commands) do
			warn(Command);warn(CommandTable)
		end
	end)
end)

Step Four

Once you know that it’s finding commands, and showing said commands in the output… you now set up an AdminTable. This is a very simple process, you can go off of UserName, or UserId. The consensus is to use the UserId as that can’t be changed, however; use what you are most comfortable with. Put this table at the very top of your script; this is how your entire code should look so far;

Scripting Example
local Admins = {"Just2Terrify"}

local Commands = {
	["Example Command"] = { --[["Title of the command"]]
		CommandPhrases = {"example1","example2","ex1"}; --[[Add the phrases you want to use to trigger the event.]]
		Level = 6, --[[This is the level of admin needed to use the command.]]
		Active = true, --[[This determines if the command is able to be used or not.]]
		Description = "This is where the commands description will be.", --[[This is where you describe what the command is supposed to do.]]
		Function = function(plr) --[[This is the function that you need to call to run the command.]]
			warn("Example Command in FunCommands Ran")
		end}
	;
	
}


game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		for Command,CommandTable in pairs(Commands) do
			warn(Command);warn(CommandTable)
		end
	end)
end)

Step Five

Create a check to make sure the player is an admin, here’s how you’d do that;

Scripting Example
game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		for Index,Value in pairs (Admins) do
			if plr.UserId == Value or string.lower(plr.Name) == string.lower(Value) then
				for Command,CommandTable in pairs(Commands) do
					warn(Command);warn(CommandTable)
				end
			end
		end
	end)
end)

Step Six

Now, you’d want to check if the message sent is a command phrase. I went ahead and did the liberty of creating the check… I will explain how I did it below the code:

Scripting Example
game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		local messageSent = message;local checkForMultipleCmds = string.split(messageSent," | ") or messageSent
		for Ind,SeparateCommand in pairs(checkForMultipleCmds) do
			local searchForCommand = string.split(SeparateCommand, " ")[1] or SeparateCommand;local commandSent = string.split(searchForCommand,Prefix)[2]
			if commandSent then
				for Index,Value in pairs (Admins) do
					if plr.UserId == Value or string.lower(plr.Name) == string.lower(Value) then
						for Command,CommandTable in pairs(Commands) do
							for TableName,Phrase in pairs(CommandTable.CommandPhrases) do
								if string.lower(Phrase) == string.lower(commandSent) then
									warn("COMMAND SENT!");warn(SeparateCommand)
								end
							end
						end
					end
				end
			end
		end
	end)
end)

NOTE

The final code was altered to send the original message through the function as the message was originally sent. This makes it where you can send messages to other people without everything being lowercase.

You’ll see that there’s a bunch of extra in pairs tables, and variables. I’ve removed cap-sensitivity to the commands by making them all lowercase in the Chatted function. I’ve also made it so you’re able to send multiple messages by putting a | at the end of each command with a space on each side. (example: /ex1 yes | /ex1 no)

Step Seven

If the message is sent by an admin, and is a recongized command, and is active; fire the function of the command. Here’s how you’d do that;

Scripting Example
game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		local checkForMultipleCmds = string.split(message," | ") or message
		for Ind,SeparateCommand in pairs(checkForMultipleCmds) do
			local searchForCommand = string.split(SeparateCommand, " ")[1] or SeparateCommand;local commandSent = string.split(searchForCommand,Prefix)[2]
			if commandSent then
				for Index,Value in pairs (Admins) do
					if plr.UserId == Value or string.lower(plr.Name) == string.lower(Value) then
						for Command,CommandTable in pairs(Commands) do
							for TableName,Phrase in pairs(CommandTable.CommandPhrases) do
								if CommandTable.Active and string.lower(Phrase) == string.lower(commandSent) then
									CommandTable.Function(string.split(SeparateCommand,commandSent.." ")[2])
								end
							end
						end
					end
				end
			end
		end
	end)
end)

This will now send anything after the command and a space; to the function. Example:


Final Script

Here’s what the code should look like now;

local Admins = {"Just2Terrify"}
local Prefix = "/"

local Commands = {
	["Example Command"] = { --[["Title of the command"]]
		CommandPhrases = {"example1","example2","ex1"}; --[[Add the phrases you want to use to trigger the event.]]
		Level = 6, --[[This is the level of admin needed to use the command.]]
		Active = true, --[[This determines if the command is able to be used or not.]]
		Description = "This is where the commands description will be.", --[[This is where you describe what the command is supposed to do.]]
		Function = function(Args) --[[This is the function that you need to call to run the command.]]
			warn(Args)
		end}
	;
	
}


game.Players.PlayerAdded:Connect(function(plr)
	plr.Chatted:Connect(function(message)
		local checkForMultipleCmds = string.split(message," | ") or message
		for Ind,SeparateCommand in pairs(checkForMultipleCmds) do
			local searchForCommand = string.split(SeparateCommand, " ")[1] or SeparateCommand;local commandSent = string.split(searchForCommand,Prefix)[2]
			if commandSent then
				for Index,Value in pairs (Admins) do
					if plr.UserId == Value or string.lower(plr.Name) == string.lower(Value) then
						for Command,CommandTable in pairs(Commands) do
							for TableName,Phrase in pairs(CommandTable.CommandPhrases) do
								if CommandTable.Active and string.lower(Phrase) == string.lower(commandSent) then
									CommandTable.Function(string.split(SeparateCommand,commandSent.." ")[2])
								end
							end
						end
					end
				end
			end
		end
	end)
end)

Now, you simply need to code the commands to do what you desire. I hope this helps you, and others in the future.


I made this with the intention of answering @ShermaanCat’s post How to code a good admin system?, then realized I spent four hours writing this; and put way too much detail into it. I figured it would be a better tutorial at the point it’s at. This is my first tutorial, so if you’d like more information on a certain section; let me know.


2024-07-12T04:00:00Z

11 Likes

I highly suggest writing it rather like below, so that it’s more readable.
Furthermore I highly suggest using more functions so it does not look like a mess.
Neither do you use Ind or Index so I replaced it with _
Furthermore you have not used the Level parameter value even.

local PlayerService = game:GetService("Players")
local commandsSplit, parameterSplit = " | ", " "

local Commands = {
	["Example Command"] = { --[["Title of the command"]]
		CommandPhrases = {"example1","example2","ex1"}; --[[Add the phrases you want to use to trigger the event.]]
		Level = 6, --[[This is the level of admin needed to use the command.]]
		Active = true, --[[This determines if the command is able to be used or not.]]
		Description = "This is where the commands description will be.", --[[This is where you describe what the command is supposed to do.]]
		Function = function(plr, arg1, arg2, ...) --[[This is the function that you need to call to run the command.]]
			warn(plr, arg1, arg2, ...)
		end
	};
	
}


-- Previously if you had multiple commands with the same phrases they all would run
local function findFirstCommand(cmdPhrase)
	for _, CommandTable in pairs(Commands) do
	if not CommandTable.Active then continue end -- you should have done this sooner, like so
		for _, Phrase in pairs(CommandTable.CommandPhrases) do
			if string.lower(Phrase) ~= string.lower(commandSent) then continue end
			return CommandTable
		end
	end
end

local function isPlayerAdmin(plr)
	for _, adminKey in pairs (Admins) do
		if plr.UserId ~= adminKey and string.lower(plr.Name) ~= string.lower(adminKey) then continue end
		return true
	end
end

local function onPlayerAdded(plr)
	plr.Chatted:Connect(function(message)
		-- If statement to check if the first letter is even a /
		local playerCommands = string.split(message, commandsSplit) or message
		for _, playerCommand in pairs(playerCommands) do
			local commandWithParameters = string.split(playerCommand, parameterSplit) 
			local commandPhrase = string.split(commandWithParameters[1] or playerCommand, Prefix)[2]
			if not commandPhrase or commandPhrase == "" then continue end 

			if not isPlayerAdmin(plr) then return end -- rather use continue here when you are using levels
			local commandInfo = findFirstCommand(commandPhrase)
			if not commandInfo then continue end -- Warning: Unable to find command
			-- Remove the first parameter
			if commandWithParameters then table.remove(commandWithParameters, 1) end
			commandInfo.Function(plr, commandWithParameters and table.unpack(commandWithParameters))
		end
	end)
end

for _, player in pairs(PlayerService:GetPlayers()) do
   onPlayerAdded(player)
end
PlayerService.PlayerAdded:Connect(onPlayerAdded)
2 Likes

this could be improved by using TextChatCommands, which is in my opinion easier to script and has Autocomplete support; albeit limited, and It’s built-in and could be updated in the near future to support more features

1 Like

Although you are correct, I don’t feel comfortable promoting a system that isn’t complete, this being the new chat system. There needs to be much more customization added to TextChatCommands, and chat messaging in general before I feel comfortable enough to endorse said system.

2 Likes

you should use table.find instead of looping so much

2 Likes

Uh, thing is this loops the command.

1 Like

Tested it with a simple admin command that just inserted the players name into the admin table, ended up looping it and crashing the game

Apologies, but I have not experienced this issue… could you send your code to me so I can review it?

If you clone the example command, set it up, and make it admin a player by inserting their name via table.insert, it loops and blows up your studio. This is what crashed my game with 20 players.

The code is the same to yours, just a new command which did table.insert(Admins,Username)

if game.Players:FindFirstChild(Args) then
table.Insert(Admins,Args)
end

  • Script exausted

Thank you for bringing this to my attention, I’ve updated my coding to fix the looping issue.

You should no longer have this issue once you’ve updated the code to my new set of code.