Warn System with Discord Embed

Hey there developers!

Do you ever have trouble creating a warn system for your admin panels? Or would you like to make one but don’t know how to? This tutorial is perfect for you!

Today we will go through each and every step on How to create a warn system along with discord embeds (Webhooks).

This tutorial does not contain user permissions and the permissions given to certain players that can use the warn system yet. It will be added in the future for sure.

This tutorial is divided in different sections. Be sure to read every section carefully.

What is a warn system?

A warning system is an essential tool in admin panels. Suppose a player in your game attempts to break the rules but isn’t as severe. Instead of banning the person or kicking him from the game. You can warn that player through an admin Panel. On a certain amount of warns, the person shall be punished.

Attention!

  • Do not share your webhook’s URL with anyone!
  • If you are banned from your own game by accident, be sure to change the name of the BanDataStore!

If you have any issues, reply down below.

After this tutorial you will be able to

  • Warn a player.
  • Store player’s warns.
  • Send a discord embed notifying you who was warned and with what reason.
  • Script auto temporary ban after a certain amount of warns have been given to a player.

Tutorial

Be sure to understand the code first!

Important

Be sure to Publish your game. Go to your game settings, go to Security and enable Api Service and HTTP Requests!

Setting up our Webhook
  • The first step would be creating yourself a discord server.

  • Head on to the Server Settings of your server.

  • Then click Integrations

  • Click on Webhooks

  • If you haven’t already created a webhook. Then click New Webhook

  • Enter the Valid information. Your Webhook should have information like this.

  • Optional: You can change the webhook name to whatever you want, the Bot’s Profile Picture. You can also select any channel in your server you want the bot to send the embed in.

  • After you have completed creating your webhook. Click Save Changes. And then Copy your webhook’s URL by clicking Copy Webhook URL as we need it later!

Setting up Warning GUI
  • Open Roblox Studio and head into your game.
  • Insert a ScreenGui inside StarterGui and name it to whatever you want. I named it WarnSystem
  • You will need a few essential things in it.
  • Insert a Frame inside the ScreenGui that we created. Add a TextBox named PlayerName.
  • Insert another TextBox inside the Frame and name it as Reason
  • Last but not the least create a TextButton named WarnButton
  • You can edit the GUI to your expectations.
  • Mine looks something like this (I am bad at GUIs)
  • Explorer GUI
Creating Remote Events
  • Insert a Remote Event inside the ReplicatedStorage and name it as Warn
    s
Scripting our Warn System

Insert a LocalScript inside our WarnButton in the StarterGui and type out this code -

  local usernameBox = script.Parent.Parent.PlayerName --getting our playername textbox
local reasonBox = script.Parent.Parent.Reason --getting our warn textbox
local remoteEvent = game:GetService("ReplicatedStorage").Warn

script.Parent.MouseButton1Click:Connect(function() --when our button is clicked
	remoteEvent:FireServer(tostring(usernameBox.Text), tostring(reasonBox.Text)) --fires the server and passes player username and warn reason
end)

Now we insert a Server Script inside ServerScript Service named WarnHandler. You can choose any name you like.

Type this code inside it -

local WarnDataStore = game:GetService("DataStoreService"):GetDataStore("WarnDataStore") --setting up our datastore
local TempBanStore = game:GetService("DataStoreService"):GetDataStore("BanDataStore") --setting up our ban datastore.
local HttpsService = game:GetService("HttpService") --getting httpservice
local WebhookURL = "your webhook's url here" --url of our webhook that we copied some time ago

game:GetService("ReplicatedStorage").Warn.OnServerEvent:Connect(function(moderator, username, reason) --when event is fired. Moderator is the player that clicks the button
	local Success, Result = pcall(function() --getting player's warn data using a pcall
		return WarnDataStore:GetAsync(tostring(username));
	end)
	if Result then
		local allWarns = Result.Warns --table of all warns the player has got
		print(Result) --prints player warn data (optional)
		if Result ~= nil then --checks if player has warn data
			table.insert(allWarns, reason) --inserts the warn to the table
			
			local Success, Error = pcall(function() --pcall that saves player's warn data
				WarnDataStore:SetAsync(tostring(username),{Warns = allWarns})
			end)
			
			if Success then --if player warn data was stored
				local embed = --setting up our embed
					{
						["content"] = "",
						["embeds"] = {{
							["title"] = "**Action**: Warn",
							["description"] = "A player was warned in **Game name**",
							["type"] = "rich",
							["color"] = 15105570,
							["fields"] = {
								{
									["name"] = "**Warned**",
									["value"] = username,
									["inline"] = false
								},
								{
									["name"] = "**Reason**",
									["value"] = reason,
									["inline"] = false
								},
								{
									["name"] = "**Number of Warns**",
									["value"] = #allWarns,
									["inline"] = false
								},
								{
									["name"] = "**Moderator**",
									["value"] = moderator.Name,
									["inline"] = false
								}
							},
							["footer"] = {
								["text"] = "Log Type: Manual Warn", }
						}}
					}
				local embed = HttpsService:JSONEncode(embed)
				HttpsService:PostAsync(WebhookURL, embed) --sends the embed to your discord server's channel
			else --if player warn data could not be saved
				print(Error)
			end
		else --if player warn data didnt exist earlier
			local Success, Error = pcall(function()  --creating and storing player warn data using a pcall
				WarnDataStore:SetAsync(tostring(username),{Warns = {reason}})
			end)
			if not Success then --if player warn data failed to save
				print(Error)
			end
			local embed = --setting up our embed
				{
					["content"] = "",
					["embeds"] = {{
						["title"] = "**Action**: Warn",
						["description"] = "A player was warned in **Game name**",
						["type"] = "rich",
						["color"] = 15105570,
						["fields"] = {
							{
								["name"] = "**Warned**",
								["value"] = username,
								["inline"] = false
							},
							{
								["name"] = "**Reason**",
								["value"] = reason,
								["inline"] = false
							},
							{
								["name"] = "**Moderator**",
								["value"] = moderator.Name,
								["inline"] = false
							}
						},
						["footer"] = {
							["text"] = "Log Type: Manual Warn", }
					}}
				}

			local embed = HttpsService:JSONEncode(embed)
			HttpsService:PostAsync(WebhookURL, embed) --sending our embed to the discord channel
		end
	end
end) 

Now we have a Warn System with a datastore complete!

Scripting our Automatic Ban System and Moderator Permissions

Now we need a BanDataStore to store player ban data. At a certain amount of warns the player shall be banned from the game for 1 whole day (24 Hours). Here i take the amount of warns to be a multiple of 3. Meaning every 3rd warn a player gets he/she will be banned from the game for 1 whole day. Example: I get 3 warns. I get banned for 1 day. Then i get total 6 warns, again i get banned for 1 day.

We will now make a few edits inside our WarnHandler Script. We will also edit the permissions of the players that can use the warn system aka the moderators

We add these lines of code to our already existing code -

	if #allWarns%3 == 0 then --checks if player has the correct amount of warns
		local Success, result = pcall(function() --getting player ban data
			return TempBanStore:GetAsync(tostring(username, "TempBan"));
		end)
		if not Success then --if player data could not be loaded
			print("Player ban data could not be loaded.")
		end
		if Success then
			print(result) --prints the player's ban data (optional)
			if result ~= nil then --checks if player is already banned
				print("The user is already banned")
			else --if not then
				game.Players[username]:Kick("You have been temporarily Banned for 1 day"); --kicks the player
				local Success, Error = pcall(function() --saving player ban data for 1 day
					TempBanStore:SetAsync(tostring(username), {BanStart = os.time(), BanDuration = 86400, BanReason = "Multiple Warns"});
				end)
				local embed =  --setting up our embed
					{
						["content"] = "",
						["embeds"] = {{
							["title"] = "**Action**: Temp Ban",
							["description"] = "A player was temporarily Banned from **Game name**",
							["type"] = "rich",
							["color"] = 15158332,
							["fields"] = {
								{
									["name"] = "**Banned**",
									["value"] = username,
									["inline"] = false
								},
								{
									["name"] = "**Duration**",
									["value"] = "1 day",
									["inline"] = false
								},
								{
									["name"] = "**Reason**",
									["value"] = "Multiple Warns",
									["inline"] = false
								},
								{
									["name"] = "**Moderator**",
									["value"] = moderator.Name,
									["inline"] = false
								}
							},
							["footer"] = {
								["text"] = "Log Type: Temporary Ban", }
						}}
					}
				local embed = HttpsService:JSONEncode(embed)

				HttpsService:PostAsync(WebhookURL, embed) --sends our embed to our discord server

			end
		end
	end

Your final WarnHandler script should look like this -

  local WarnDataStore = game:GetService("DataStoreService"):GetDataStore("WarnDataStore") --setting up our datastore
local TempBanStore = game:GetService("DataStoreService"):GetDataStore("BanDataStore") --setting up our ban datastore.
local HttpsService = game:GetService("HttpService") --getting httpservice
local WebhookURL = "your webhook's url here" --url of our webhook that we copied some time ago


local moderators = {"player1", "jaipack17", "player3", "player4"} --here we write our moderators' names

game:GetService("ReplicatedStorage").Warn.OnServerEvent:Connect(function(moderator, username, reason) --when event is fired. Moderator is the player that clicks the button
	local check = table.find(moderators, tostring(moderator))
	print(moderator)
	if check == nil then
		print("Player is not a moderator")
		return
	end
	local Success, Result = pcall(function() --getting player's warn data using a pcall
		return WarnDataStore:GetAsync(tostring(username), "WarnData");
	end)
	if Result then
		local allWarns = Result.Warns --table of all warns the player has got
		print(Result) --prints player warn data (optional)
		if Result ~= nil then --checks if player has warn data
			table.insert(allWarns, reason) --inserts the warn to the table
			
			local Success, Error = pcall(function() --pcall that saves player's warn data
				WarnDataStore:SetAsync(tostring(username),{Warns = allWarns})
			end)
			
			if Success then --if player warn data was stored
				local embed = --setting up our embed
					{
						["content"] = "",
						["embeds"] = {{
							["title"] = "**Action**: Warn",
							["description"] = "A player was warned in **Game name**",
							["type"] = "rich",
							["color"] = 15105570,
							["fields"] = {
								{
									["name"] = "**Warned**",
									["value"] = username,
									["inline"] = false
								},
								{
									["name"] = "**Reason**",
									["value"] = reason,
									["inline"] = false
								},
								{
									["name"] = "**Number of Warns**",
									["value"] = #allWarns,
									["inline"] = false
								},
								{
									["name"] = "**Moderator**",
									["value"] = moderator.Name,
									["inline"] = false
								}
							},
							["footer"] = {
								["text"] = "Log Type: Manual Warn", }
						}}
					}
				local embed = HttpsService:JSONEncode(embed)
				HttpsService:PostAsync(WebhookURL, embed) --sends the embed to your discord server's channel
				if #allWarns%3 == 0 then --checks if player has the correct amount of warns
					local Success, result = pcall(function() --getting player ban data
						return TempBanStore:GetAsync(tostring(username, "TempBan"));
					end)
					if not Success then --if player data could not be loaded
						print("Player ban data could not be loaded.")
					end
					if Success then
						print(result) --prints the player's ban data (optional)
						if result ~= nil then --checks if player is already banned
							print("The user is already banned")
						else --if not then
							if game.Players:FindFirstChild(username) then
								game.Players[username]:Kick("You have been temporarily Banned for 1 day"); --kicks the player
							end
							local Success, Error = pcall(function() --saving player ban data for 1 day
								TempBanStore:SetAsync(tostring(username), {BanStart = os.time(), BanDuration = 86400, BanReason = "Multiple Warns"});
							end)
							local embed =  --setting up our embed
								{
									["content"] = "",
									["embeds"] = {{
										["title"] = "**Action**: Temp Ban",
										["description"] = "A player was temporarily Banned from **Game name**",
										["type"] = "rich",
										["color"] = 15158332,
										["fields"] = {
											{
												["name"] = "**Banned**",
												["value"] = username,
												["inline"] = false
											},
											{
												["name"] = "**Duration**",
												["value"] = "1 day",
												["inline"] = false
											},
											{
												["name"] = "**Reason**",
												["value"] = "Multiple Warns",
												["inline"] = false
											},
											{
												["name"] = "**Moderator**",
												["value"] = moderator.Name,
												["inline"] = false
											}
										},
										["footer"] = {
											["text"] = "Log Type: Temporary Ban", }
									}}
								}
							local embed = HttpsService:JSONEncode(embed)

							HttpsService:PostAsync(WebhookURL, embed) --sends our embed to our discord server

						end
					end
				end
			end

			else --if player warn data could not be saved
				print("Error while saving data")
			end
			
		else --if player warn data didnt exist earlier
			local Success, Error = pcall(function()  --creating and storing player warn data using a pcall
				WarnDataStore:SetAsync(tostring(username),{Warns = {reason}})
			end)
			if not Success then --if player warn data failed to save
				print(Error)
			end
			local embed = --setting up our embed
				{
					["content"] = "",
					["embeds"] = {{
						["title"] = "**Action**: Warn",
						["description"] = "A player was warned in **Game name**",
						["type"] = "rich",
						["color"] = 15105570,
						["fields"] = {
							{
								["name"] = "**Warned**",
								["value"] = username,
								["inline"] = false
							},
							{
								["name"] = "**Reason**",
								["value"] = reason,
								["inline"] = false
							},
							{
								["name"] = "**Moderator**",
								["value"] = moderator.Name,
								["inline"] = false
							}
						},
						["footer"] = {
							["text"] = "Log Type: Manual Warn", }
					}}
				}

			local embed = HttpsService:JSONEncode(embed)
			HttpsService:PostAsync(WebhookURL, embed) --sending our embed to the discord channel
		end
end) 

Now we have setup our ban datastore!

Scripting Automatic Kick on ban

We created the ban system in the last section. Now we need to kick the player from the game everytime the player joins the game while being banned.

Create a new Server Script inside ServerScriptService and name it as BanChecker. (You can name it whatever you want)

Type this code -

local TempBanStore = game:GetService("DataStoreService"):GetDataStore("BanDataStore") --getting our ban datastore.
local HttpsService = game:GetService("HttpService") --getting httpservice
local WebhookURL = "your webhook's url here" --url of our webhook 

game.Players.PlayerAdded:Connect(function(Player) --when player is added
	local Success, Result = pcall(function() --getting player ban data
		return TempBanStore:GetAsync(tostring(Player.Name, "TempBan"));
	end)

	if Success then --if player data was loaded
		if Result then 
			print(Result) --print the player ban data (optional)
			if Result.BanStart ~= nil and Result.BanDuration ~= nil then --checks if player data is present. If yes  then
				if Result.BanStart + Result.BanDuration <= os.time() then --checks if player ban time is over (1 day)
					print("User has been unbanned") --unbans the user if ban time is over
					local embed = --setting up our embed
						{
							["content"] = "",
							["embeds"] = {{
								["title"] = "**Action**: Unbanned",
								["description"] = "A player was Unbanned from **Game name**",
								["type"] = "rich",
								["color"] = 3066993,
								["fields"] = {
									{
										["name"] = "**Unbanned**",
										["value"] = Player.Name,
										["inline"] = false
									},
									{
										["name"] = "**Past Ban Reason**",
										["value"] = Result.BanReason,
										["inline"] = false
									},
									{
										["name"] = "**Automatic Unban**",
										["value"] = "Temporary Ban Time Over",
										["inline"] = false
									},
								},
								["footer"] = {
									["text"] = "Log Type: Automatic Unban", }
							}}
						}
					local embed = HttpsService:JSONEncode(embed)

					HttpsService:PostAsync(WebhookURL, embed) --sends the embed to the discord server
					TempBanStore:RemoveAsync(tostring(Player.Name)); --removes player ban data			
				else
					Player:Kick("You have been Temporarily Banned") --if ban time is not over we kick the player
				end
		end
	end
end
end)

Yay! Our warn system is complete! If you have any doubts, feedback, more information about the same be sure to reply down below!

Thanks!

Update

Warn system now has Moderator permissions. You can set the players that have access to the warn system.

22 Likes

This is extremely helpful, Thanks!

1 Like

I will definitely use this in the future, thank you.

1 Like

Wouldn’t that be a security risk, as exploiters can get access to this local script and change it without you knowing?

It would be if there’s no sanity checks on the server-side to check if the user is authorized to use the warn system and I’m seeing none in the server-side code.

1 Like

Hi,

The tutorial does not contain that aspect of setting up the people that can use the warn system. It does not contain permission handling. My tutorial sums up the basics of making a warn system. If you would like me to add that, let me know. Do not worry about it. I’ll add the permission handler as well as sanity checks when I update the tutorial in a few hours.

Edit: Permissions have been added.

1 Like

Warn System Permissions have been added to the tutorial.

Is there a way we can setup group permissions?

I saw this in another webhook thread that discord doesn’t like people using webhooks to log stuff? Since webhooks are hosted on their servers the thousands of people having roblox webhooks is quite costly. Although I’m not 100% on this.

If you can try and use a bot instead since that doesn’t cost discord as much

1 Like

Discord isn’t meant to be a logging service in any form or fashion.

2 Likes

You’re using tostring too much and it’s unnecessary and there’s a flaw where anyone can fire the remote event and ban anyone.

2 Likes