Chat Tags not working

Im trying to make an admin panel where i can select a player and give them a tier in chat tag (for example tier 5, tier 4 etc…)

local Players           = game:GetService("Players")
local DataStoreService  = game:GetService("DataStoreService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

-- DataStore for saving player tags
local TagDataStore = DataStoreService:GetDataStore("PlayerTags_v2")

-- List of admin usernames (replace with your actual admin names)
local ADMINS = {
	"davy_tornato",   -- Replace with your username
	"AltroAdmin",     -- Add other admin usernames here
	"ahmedrocks5yt"
}

-- Tag configuration table: tagName → { Color = Color3, Priority = number }
local TAG_CONFIGS = {
	["TIER 1"] = { Color = Color3.new(1,   0.84, 0  ), Priority = 5 }, -- Gold
	["TIER 2"] = { Color = Color3.new(0.75, 0.75, 0.75), Priority = 4 }, -- Silver
	["TIER 3"] = { Color = Color3.new(0.8,  0.5,  0.2 ), Priority = 3 }, -- Bronze
	["TIER 4"] = { Color = Color3.new(0.2,  0.8,  0.2 ), Priority = 2 }, -- Green
	["TIER 5"] = { Color = Color3.new(0.6,  0.6,  0.6 ), Priority = 1 }  -- Gray
}

-- ActiveTags: [username] = tagName
local ActiveTags = {}

-- Create (or find) a folder in ReplicatedStorage for our remote events/functions
local remoteFolder = ReplicatedStorage:FindFirstChild("AdminRemotes")
if not remoteFolder then
	remoteFolder = Instance.new("Folder")
	remoteFolder.Name = "AdminRemotes"
	remoteFolder.Parent = ReplicatedStorage
end

-- Define or reuse each RemoteEvent / RemoteFunction
local openAdminPanelRemote = remoteFolder:FindFirstChild("OpenAdminPanel")
if not openAdminPanelRemote then
	openAdminPanelRemote = Instance.new("RemoteEvent")
	openAdminPanelRemote.Name = "OpenAdminPanel"
	openAdminPanelRemote.Parent = remoteFolder
end

local setTagRemote = remoteFolder:FindFirstChild("SetTag")
if not setTagRemote then
	setTagRemote = Instance.new("RemoteEvent")
	setTagRemote.Name = "SetTag"
	setTagRemote.Parent = remoteFolder
end

local removeTagRemote = remoteFolder:FindFirstChild("RemoveTag")
if not removeTagRemote then
	removeTagRemote = Instance.new("RemoteEvent")
	removeTagRemote.Name = "RemoveTag"
	removeTagRemote.Parent = remoteFolder
end

local getTagDataRemote = remoteFolder:FindFirstChild("GetTagData")
if not getTagDataRemote then
	getTagDataRemote = Instance.new("RemoteFunction")
	getTagDataRemote.Name = "GetTagData"
	getTagDataRemote.Parent = remoteFolder
end

local getTagConfigsRemote = remoteFolder:FindFirstChild("GetTagConfigs")
if not getTagConfigsRemote then
	getTagConfigsRemote = Instance.new("RemoteFunction")
	getTagConfigsRemote.Name = "GetTagConfigs"
	getTagConfigsRemote.Parent = remoteFolder
end

local syncTagsRemote = remoteFolder:FindFirstChild("SyncTags")
if not syncTagsRemote then
	syncTagsRemote = Instance.new("RemoteEvent")
	syncTagsRemote.Name = "SyncTags"
	syncTagsRemote.Parent = remoteFolder
end

-- Utility: check if a given player is in the ADMINS list
local function isAdmin(player)
	for _, adminName in ipairs(ADMINS) do
		if player.Name == adminName then
			return true
		end
	end
	return false
end

-- Load a saved tag for this username from DataStore
local function loadPlayerTag(username)
	local success, result = pcall(function()
		return TagDataStore:GetAsync(username)
	end)
	if success and result then
		return result
	end
	return nil
end

-- Save (or remove) a tag for this username in DataStore
local function savePlayerTag(username, tagName)
	local success, err = pcall(function()
		if tagName and tagName ~= "" then
			TagDataStore:SetAsync(username, tagName)
		else
			TagDataStore:RemoveAsync(username)
		end
	end)
	if not success then
		warn("Error saving tag for " .. username .. ": " .. tostring(err))
	end
end

-- Create an overhead BillboardGui tag above the player's head
local function createOverheadTag(player, tagName)
	if not player.Character or not player.Character:FindFirstChild("Head") then
		return
	end

	-- Remove any pre‐existing “AdminTag”
	local existingTag = player.Character.Head:FindFirstChild("AdminTag")
	if existingTag then
		existingTag:Destroy()
	end

	-- Build a new BillboardGui under the Head
	local billboardGui = Instance.new("BillboardGui")
	billboardGui.Name = "AdminTag"
	billboardGui.Adornee = player.Character.Head
	billboardGui.Size = UDim2.new(0, 200, 0, 50)
	billboardGui.StudsOffset = Vector3.new(0, 2, 0)
	billboardGui.AlwaysOnTop = true
	billboardGui.Parent = player.Character.Head

	local frame = Instance.new("Frame")
	frame.Size = UDim2.new(1, 0, 1, 0)
	frame.BackgroundColor3 = Color3.new(0, 0, 0)
	frame.BackgroundTransparency = 0.3
	frame.BorderSizePixel = 0
	frame.Parent = billboardGui

	local corner = Instance.new("UICorner")
	corner.CornerRadius = UDim.new(0, 8)
	corner.Parent = frame

	local textLabel = Instance.new("TextLabel")
	textLabel.Size = UDim2.new(1, 0, 1, 0)
	textLabel.BackgroundTransparency = 1
	textLabel.Text = "[" .. tagName .. "]"
	textLabel.TextColor3 = TAG_CONFIGS[tagName].Color
	textLabel.TextScaled = true
	textLabel.Font = Enum.Font.SourceSansBold
	textLabel.Parent = frame

	local stroke = Instance.new("UIStroke")
	stroke.Color = Color3.new(0, 0, 0)
	stroke.Thickness = 2
	stroke.Parent = textLabel
end

-- Remove the overhead tag from a player’s head
local function removeOverheadTag(player)
	if player.Character and player.Character:FindFirstChild("Head") then
		local existingTag = player.Character.Head:FindFirstChild("AdminTag")
		if existingTag then
			existingTag:Destroy()
		end
	end
end

-- Assign a tag (both overhead & persistence) to a player
local function applyTagToPlayer(username, tagName)
	ActiveTags[username] = tagName
	savePlayerTag(username, tagName)

	local player = Players:FindFirstChild(username)
	if player then
		createOverheadTag(player, tagName)
	end

	-- Sync the full ActiveTags table to every client
	syncTagsRemote:FireAllClients(ActiveTags)
end

-- Remove a player’s tag (both overhead & persistence)
local function removeTagFromPlayer(username)
	local oldTag = ActiveTags[username]
	ActiveTags[username] = nil
	savePlayerTag(username, nil)

	local player = Players:FindFirstChild(username)
	if player then
		removeOverheadTag(player)
	end

	-- Sync the updated ActiveTags to every client
	syncTagsRemote:FireAllClients(ActiveTags)
end

-- RemoteEvent: Admin sets a tag on a target player
setTagRemote.OnServerEvent:Connect(function(player, targetUsername, tagName)
	if not isAdmin(player) then
		return
	end
	if TAG_CONFIGS[tagName] then
		applyTagToPlayer(targetUsername, tagName)
		print("Admin " .. player.Name .. " set tag " .. tagName .. " on " .. targetUsername)
	end
end)

-- RemoteEvent: Admin removes a tag from a target player
removeTagRemote.OnServerEvent:Connect(function(player, targetUsername)
	if not isAdmin(player) then
		return
	end
	removeTagFromPlayer(targetUsername)
	print("Admin " .. player.Name .. " removed tag from " .. targetUsername)
end)

-- RemoteFunction: Admin GUI asks for current tag data (online players, configs, ActiveTags)
getTagDataRemote.OnServerInvoke = function(player)
	if not isAdmin(player) then
		return {}
	end

	local onlinePlayers = {}
	for _, p in ipairs(Players:GetPlayers()) do
		table.insert(onlinePlayers, {
			Name = p.Name,
			Tag  = ActiveTags[p.Name]
		})
	end

	return {
		OnlinePlayers = onlinePlayers,
		TagConfigs    = TAG_CONFIGS,
		ActiveTags    = ActiveTags
	}
end

-- RemoteFunction: Admin GUI asks for TAG_CONFIGS only
getTagConfigsRemote.OnServerInvoke = function(player)
	if not isAdmin(player) then
		return {}
	end
	return TAG_CONFIGS
end

-- When a new player joins:
Players.PlayerAdded:Connect(function(player)
	-- 1) Listen for chat command “/admin” to open the admin panel
	player.Chatted:Connect(function(message)
		if isAdmin(player) and message:lower() == "/admin" then
			openAdminPanelRemote:FireClient(player)
		end
	end)

	-- 2) After a short delay, send existing ActiveTags to just that client
	task.wait(2)
	syncTagsRemote:FireClient(player, ActiveTags)

	-- 3) After DataStore loads, re-apply any saved tag to their character
	task.wait(5)
	local savedTag = loadPlayerTag(player.Name)
	if savedTag and TAG_CONFIGS[savedTag] then
		ActiveTags[player.Name] = savedTag

		player.CharacterAdded:Connect(function()
			task.wait(1)
			createOverheadTag(player, savedTag)
		end)
		if player.Character then
			createOverheadTag(player, savedTag)
		end
		print("Loaded saved tag for " .. player.Name .. ": " .. savedTag)
	else
		print("No saved tag for " .. player.Name)
	end
end)

-- When a player leaves, you may optionally clear ActiveTags (DataStore still holds their last tag)
Players.PlayerRemoving:Connect(function(player)
	-- ActiveTags[player.Name] = nil
end)

print("TagManager loaded successfully!")

---- This is a script in serverscriptservice
local ChatTags = {}

– Cached ChatService once found
local _cachedChatService = nil

– Attempts to locate and require ChatServiceRunner/ChatService up to 10 times (1 second apart).
– Returns the ChatService module or nil if not found.
local function getChatService()
if _cachedChatService then
return _cachedChatService
end

for attempt = 1, 10 do
	local csrFolder = game:GetService("ServerScriptService"):FindFirstChild("ChatServiceRunner")
	if csrFolder then
		local csModule = csrFolder:FindFirstChild("ChatService")
		if csModule and csModule:IsA("ModuleScript") then
			local success, module = pcall(require, csModule)
			if success then
				_cachedChatService = module
				return module
			else
				warn("ChatTags: Failed to require ChatService (pcall error).")
				return nil
			end
		end
	end
	wait(1)
end

warn("ChatTags: Could not find ChatServiceRunner/ChatService after 10 seconds.")
return nil

end

– Adds a chat tag (tagString) with the given color3 to the specified player.
function ChatTags.Add(player, tagString, color3)
if not player or typeof(tagString) ~= “string” then
return
end

local ChatService = getChatService()
if not ChatService then
	return
end

local success, speaker = pcall(function()
	return ChatService:GetSpeaker(player.Name)
end)

if success and speaker then
	-- Default ChatServiceRunner uses AddExtraData for “Tags” and “TagColor”
	speaker:AddExtraData("Tags", tagString)
	speaker:AddExtraData("TagColor", {color3.R, color3.G, color3.B})
else
	warn("ChatTags.Add: Could not get speaker for " .. tostring(player.Name))
end

end

– Removes any chat tag (“Tags” and “TagColor” extra data) from the specified player.
function ChatTags.Remove(player, tagString)
if not player then
return
end

local ChatService = getChatService()
if not ChatService then
	return
end

local success, speaker = pcall(function()
	return ChatService:GetSpeaker(player.Name)
end)

if success and speaker then
	speaker:RemoveExtraData("Tags")
	speaker:RemoveExtraData("TagColor")
else
	warn("ChatTags.Remove: Could not get speaker for " .. tostring(player.Name))
end

end

return ChatTags
-----This is a module script in server script service
local Players = game:GetService(“Players”)
local ReplicatedStorage = game:GetService(“ReplicatedStorage”)

TextChatService must be enabled in Game Settings → Chat
local TextChatService = game:GetService(“TextChatService”)

local localPlayer = Players.LocalPlayer

– Wait for our AdminRemotes folder
local remoteFolder = ReplicatedStorage:WaitForChild(“AdminRemotes”)
local syncTagsRemote = remoteFolder:WaitForChild(“SyncTags”)
local getTagConfigsRemote = remoteFolder:WaitForChild(“GetTagConfigs”)

– Local copies:
– activeTags[username] = tagName
– tagConfigs[tagName] = { Color = Color3, Priority = number }
local activeTags = {}
local tagConfigs = {}

– Helper: Convert Color3 → HEX string (e.g. Color3.new(1,0.84,0) → “#FFD900”)
local function color3ToHex(c)
local r = math.clamp(math.floor(c.R * 255 + 0.5), 0, 255)
local g = math.clamp(math.floor(c.G * 255 + 0.5), 0, 255)
local b = math.clamp(math.floor(c.B * 255 + 0.5), 0, 255)
return string.format(“#%02X%02X%02X”, r, g, b)
end

– 1) Fetch TAG_CONFIGS from the server
– (Non-admins will get an empty table; admins receive the full TAG_CONFIGS data.)
local configs = getTagConfigsRemote:InvokeServer()
if typeof(configs) == “table” then
tagConfigs = configs
else
tagConfigs = {}
end

– 2) Listen for SyncTags from the server; always replace our local activeTags map
syncTagsRemote.OnClientEvent:Connect(function(newActiveTags)
activeTags = {}
for username, tagName in pairs(newActiveTags or {}) do
activeTags[username] = tagName
end
end)

– 3) Register incoming‐message callback on TextChatService
– Prepend a colored “[Tier X]” if the sender is in activeTags
– This callback fires for every chat message that the client sees.
if Enum.TextChatMessageStatus and TextChatService.RegisterIncomingMessageCallback then
TextChatService:RegisterIncomingMessageCallback(
function(textChatMessage)
local senderName = textChatMessage.TextSource.Name
local tagName = activeTags[senderName]
if tagName then
local cfg = tagConfigs[tagName]
if cfg and cfg.Color then
local hex = color3ToHex(cfg.Color)
textChatMessage.Text = (“[%s] %s”):format(
hex,
tagName,
textChatMessage.Text
)
else
– Fallback if color not found
textChatMessage.Text = (“[%s] %s”):format(tagName, textChatMessage.Text)
end
end
end
)
else
– If the enum isn’t available, TextChatService isn’t enabled.
warn(“ClientTagHandler: TextChatService or Enum.TextChatMessageEvent is not available. Chat tagging will not work. Make sure ChatType is set to BubbleChat/Classic (TextChat).”)
end

print(“ClientTagHandler initialized.”)
-----This is a script in StarterPlayerScripts
Also Tell me if i have to use TextChatService or Legacy because i tried with both

Your script here is using the LegacyChatService to obtain the ChatService module in ServerScriptService.

You can keep using LegacyChatService if you wan’t but I don’t recommend you using it since the legacy chat has many flaws.

1 Like

the thing is that the script doesnt work, the chat tag wont appear in chat

1 Like