Difference between revisions of "Text Filtering"

[checked revision][checked revision]
(External Sources: reorganized a bit)
 
Line 2: Line 2:
  
 
Because filtering is so crucial for a safe environment, Roblox actively moderates the content of games to make sure they meet certain standards. If a game is reported or automatically detected to not use filtering, that game will be shut down until the developer takes the proper steps to apply filtering.
 
Because filtering is so crucial for a safe environment, Roblox actively moderates the content of games to make sure they meet certain standards. If a game is reported or automatically detected to not use filtering, that game will be shut down until the developer takes the proper steps to apply filtering.
 
{{#widget:YouTube|id=_iGq90ywfzg}}
 
  
 
=How to filter text=
 
=How to filter text=
Text filtering is done with two functions of the Chat service: FilterStringAsync and FilterStringForBroadcast. Both of these functions will take a string of text as input and return a string that was filtered for the appropriate audience.
+
Text filtering is done with the {{api|TextService.FilterStringAsync}} function of {{api|TextService}}. This function will take a string of text as input and the userId of the player who created the text and return a {{api|TextFilterResult}} object that can be used to distribute the filtered string.
  
{{EmphasisBox|While FilterStringAsync and FilterStringForBroadcast can be called both on the client and server, it is recommended to filter on the server.|yellow|dark=yes}}
+
{{EmphasisBox| FilterStringAsync should be called on the server as it will fail if called on the client.|yellow|dark=yes}}
 
==FilterStringAsync==
 
==FilterStringAsync==
FilterStringAsync is used to filter a string intended for a single user to view. It takes three arguments: the string to filter, the player who authored the text, and the intended recipient. In code this function would appear as:
+
FilterStringAsync can be used to filter a string intended for a single user to view or for broadcasting a string globally. It takes two arguments: the string to filter and the id of the player who authored the text. In code this function would appear as:
  
 
{{Code|=
 
{{Code|=
local filteredText = game:GetService("Chat"):FilterStringAsync(text, fromPlayer, toPlayer)
+
local TextService = game:GetService("TextService")
 +
local filteredTextResult = TextService:FilterStringAsync(text, fromPlayerId)
 
}}
 
}}
  
This function is most commonly used when sending private messages, but in general should be used anytime a string of text is meant to be viewed by a specific player.  
+
This function can be used to filter strings meant for specific users or all users in chat and non-chat situations. {{api|TextFilterResult}}, the object returned by the function, has three methods that can be called:{{api|TextFilterResult.GetChatForUserAsync}}, {{api|TextFilterResult.GetNonChatStringForBroadcastAsync}}, {{api|TextFilterResult.GetNonChatStringForUserAsync}}.
  
Note that this function can fail on occasion as it makes a web call internally, so it should always be wrapped in a pcall.
+
Note that FilterStringAsync and all of the TextFilterResult functions can fail on occasion as they make web calls internally, so they should always be wrapped in pcalls.
  
 
{{Code|=
 
{{Code|=
 +
local TextService = game:GetService("TextService")
 
local filteredText = ""
 
local filteredText = ""
local success, message = pcall(function()
+
local success, errorMessage = pcall(function()
filteredText = game:GetService("Chat"):FilterStringAsync(text, fromPlayer, toPlayer)
+
filteredTextResult = TextService:FilterStringAsync(text, fromPlayerId)
 
end)
 
end)
 
if not success then
 
if not success then
warn("Error filtering text:" .. text.. " : " .. message)
+
warn("Error filtering text:", text, ":", errorMessage)
 
-- Put code here to handle filter failure
 
-- Put code here to handle filter failure
 
end
 
end
Line 34: Line 34:
  
 
===Example===
 
===Example===
This example sets up a widget that allows a player to send a message to another. Such a widget needs at least two scripts: a local script to handle input and displaying messages, and a script to filter the messages on the server.
+
This example sets up a widget that allows a player to send a message to another. Such a widget needs at least two scripts: a local script to handle input and displaying messages, and a script to filter the messages on the server. Because this example has a player sending a message to another specific player, the GetChatForUserAsync function should be used.
 +
 
 +
{{EmphasisBox| This tutorial assumes that a GUI and a remote event has been set up already. |yellow|dark=yes}}
 +
 
 +
[https://www.roblox.com/games/1076221107/Message-Passing-With-Filtering-Example -Text Filtering Example]
  
 
{{Code|=
 
{{Code|=
Line 42: Line 46:
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
  
local screen = script.Parent
+
local player = Players.LocalPlayer
 +
local playerGui = player:WaitForChild("PlayerGui")
 +
local screen = playerGui:WaitForChild("MessageScreen")
 
local sendMessageEvent = ReplicatedStorage:WaitForChild("SendPrivateMessage")
 
local sendMessageEvent = ReplicatedStorage:WaitForChild("SendPrivateMessage")
  
 
-- GUI elements for send frame
 
-- GUI elements for send frame
local sendFrame = screen.SendFrame
+
local sendFrame = screen:WaitForChild("SendFrame")
local recipientField = sendFrame.Recipient
+
local recipientField = sendFrame:WaitForChild("Recipient")
local writeMessageField = sendFrame.Message
+
local writeMessageField = sendFrame:WaitForChild("Message")
local sendButton = sendFrame.Send
+
local sendButton = sendFrame:WaitForChild("Send")
  
 
-- GUI elements for receive frame
 
-- GUI elements for receive frame
local receiveFrame = screen.ReceiveFrame
+
local receiveFrame = screen:WaitForChild("ReceiveFrame")
local senderField = receiveFrame.From
+
local senderField = receiveFrame:WaitForChild("From")
local readMessageField = receiveFrame.Message
+
local readMessageField = receiveFrame:WaitForChild("Message")
  
 
-- Called when send button is clicked
 
-- Called when send button is clicked
Line 86: Line 92:
  
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Chat = game:GetService("Chat")
+
local TextService = game:GetService("TextService")
  
 
local sendMessageEvent = ReplicatedStorage.SendPrivateMessage
 
local sendMessageEvent = ReplicatedStorage.SendPrivateMessage
 +
 +
local function getTextObject(message, fromPlayerId)
 +
local textObject
 +
local success, errorMessage = pcall(function()
 +
textObject = TextService:FilterStringAsync(message, fromPlayerId)
 +
end)
 +
if success then
 +
return textObject
 +
end
 +
return false
 +
end
 +
 +
local function getFilteredMessage(textObject, toPlayerId)
 +
local filteredMessage
 +
local success, errorMessage = pcall(function()
 +
filteredMessage = textObject:GetChatForUserAsync(toPlayerId)
 +
end)
 +
if success then
 +
return filteredMessage
 +
end
 +
return false
 +
end
  
 
-- Called when client sends a message
 
-- Called when client sends a message
Line 94: Line 122:
 
if message ~= "" then
 
if message ~= "" then
 
-- Filter the incoming message and send the filtered message
 
-- Filter the incoming message and send the filtered message
local filteredMessage = ""
+
local messageObject = getTextObject(message, sender.UserId)
local success, message = pcall(function()
+
filteredMessage = Chat:FilterStringAsync(message, sender, recipient)
+
if messageObject then
end)
+
local filteredMessage = getFilteredMessage(messageObject, recipient.UserId)
if success then
+
sendMessageEvent:FireClient(recipient, sender, message)
sendMessageEvent:FireClient(recipient, sender, filteredMessage)
+
 
end
 
end
 
end
 
end
Line 107: Line 134:
 
}}
 
}}
  
==FilterStringForBroadcast==
 
FilterStringForBroadcast is used to filter a string that all players in a server will see, or if the recipient is unknown at the time. This function is slightly more aggressive than FilterStringAsync as it has to cover any potential user.
 
  
FilterStringForBroadcast only takes two arguments, the string to filter and the player who authored the text.
+
===Example===
 +
This example sets up a dialog that lets a player write a message on a sign. Since anyone in the server would be able to read the sign, even players who join the game after the writing player has left, the text has to be filtered with {{api|TextFilterResult.GetNonChatStringForBroadcastAsync}}. This example assumes that a place has been set up already with all the proper elements in place. A working place has been provided below.
  
{{Code|=
+
[https://www.roblox.com/games/1076204840/Broadcast-Text-Filtering Working Example -Broadcast Text Filtering]
local filteredText = game.Chat:FilterStringForBroadcast(text, fromPlayer)
+
}}
+
 
+
This function is commonly used when displaying text to all players, such as the names of NPC companions, signs, etc.  
+
 
+
Note that this function can fail on occasion as it makes a web call internally, so it should always be wrapped in a pcall.
+
 
+
{{Code|=
+
local filteredText = ""
+
local success, message = pcall(function()
+
filteredText = game:GetService("Chat"):FilterStringForBroadcast(text, fromPlayer)
+
end)
+
if not success then
+
warn("Error filtering text:" .. text.. " : " .. message)
+
-- Put code here to handle filter failure
+
end
+
}}
+
===Example===
+
This example sets up a dialog that lets a player write a message on a sign. Since anyone in the server would be able to read the sign (even players who join the game after the writing player has left) the text has to be filtered with FilterStringForBroadcast.
+
  
 
{{Code|=
 
{{Code|=
 
-- LocalScript
 
-- LocalScript
  
 +
local Players = game:GetService("Players")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 +
 +
local player = Players.LocalPlayer
 +
local playerGui = player:WaitForChild("PlayerGui")
 +
local screen = playerGui:WaitForChild("MessageScreen")
  
 
-- GUI elements for dialog
 
-- GUI elements for dialog
local frame = script.Parent
+
local frame = screen:WaitForChild("Frame")
local box = frame.TextBox
+
local messageInput = frame:WaitForChild("Message")
local button = frame.TextButton
+
local sendButton = frame:WaitForChild("Send")
  
 
-- RemoteEvent to send text to server for filtering and display
 
-- RemoteEvent to send text to server for filtering and display
Line 148: Line 160:
 
-- Called when button is clicked
 
-- Called when button is clicked
 
local function onClick()
 
local function onClick()
local message = box.Text
+
local message = messageInput.Text
 
if message ~= "" then
 
if message ~= "" then
 
setSignText:FireServer(message)
 
setSignText:FireServer(message)
Line 155: Line 167:
 
end
 
end
  
button.MouseButton1Click:Connect(onClick)
+
sendButton.MouseButton1Click:Connect(onClick)
 
}}
 
}}
 
<br/>
 
<br/>
Line 161: Line 173:
 
-- Server Script
 
-- Server Script
  
local Chat = game:GetService("Chat")
+
--local Players = game:GetService("Players")
 +
local TextService = game:GetService("TextService")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
  
 
local sign = game.Workspace.Sign
 
local sign = game.Workspace.Sign
 +
local signTop = sign.Top
 +
local signSurfaceGui = signTop.SurfaceGui
 +
local signLabel = signSurfaceGui.SignLabel
 +
 
local setSignText = ReplicatedStorage.SetSignText
 
local setSignText = ReplicatedStorage.SetSignText
 +
 +
local function getTextObject(message, fromPlayerId)
 +
local textObject
 +
local success, errorMessage = pcall(function()
 +
textObject = TextService:FilterStringAsync(message, fromPlayerId)
 +
end)
 +
if success then
 +
return textObject
 +
elseif errorMessage then
 +
print("Error generating TextFilterResult:", errorMessage)
 +
end
 +
return false
 +
end
 +
 +
local function getFilteredMessage(textObject)
 +
local filteredMessage
 +
local success, errorMessage = pcall(function()
 +
filteredMessage = textObject:GetNonChatStringForBroadcastAsync()
 +
end)
 +
if success then
 +
return filteredMessage
 +
elseif errorMessage then
 +
print("Error filtering message:", errorMessage)
 +
end
 +
return false
 +
end
  
 
-- Fired when client sends a request to write on the sign
 
-- Fired when client sends a request to write on the sign
 
local function onSetSignText(player, text)
 
local function onSetSignText(player, text)
 
if text ~= "" then
 
if text ~= "" then
 +
-- filter the incoming message and send the filtered message
 +
local messageObject = getTextObject(text, player.UserId)
 
local filteredText = ""
 
local filteredText = ""
local success, message = pcall(function()
+
filteredText = getFilteredMessage(messageObject)
filteredText = Chat:FilterStringForBroadcast(text, player)
+
signLabel.Text = filteredText
end)
+
if success then
+
sign.Top.SurfaceGui.TextLabel.Text = filteredText
+
end
+
 
end
 
end
 
end
 
end
Line 184: Line 225:
  
 
=When to Filter Text=
 
=When to Filter Text=
Any text that a developer does not have explicit control over should be filtered. In general, this mainly refers to text that players have control over but there are a few other cases that are important to consider to make sure games are compliant with the Roblox filtering rules.
+
Any displayed text that a developer does not have explicit control over should be filtered. In general, this mainly refers to text that players have control over but there are a few other cases that are important to consider to make sure games are compliant with the Roblox filtering rules.
 
==Player Input==
 
==Player Input==
 
Any text that a player writes that is to be displayed must be filtered, no matter how the text is input or displayed. The most common way to input text is through TextBoxes, but there can be any number of ways to get text input from a player, from a custom GUI with character buttons to interactive keyboard models in the 3d space.
 
Any text that a player writes that is to be displayed must be filtered, no matter how the text is input or displayed. The most common way to input text is through TextBoxes, but there can be any number of ways to get text input from a player, from a custom GUI with character buttons to interactive keyboard models in the 3d space.
Line 192: Line 233:
 
[[File:AlternateInput1.png|800px]]
 
[[File:AlternateInput1.png|800px]]
  
Along with novel and unorthodox input methods, there are many ways of displaying text besides using TextLabels. For example, words can be spell out of 3d parts, Models with Humanoids display their name. If the content of any such display is visible to players, then the text needs to be filtered before it is displayed.
+
Along with novel and unorthodox input methods, there are many ways of displaying text besides using TextLabels. For example, words can be spelled out with 3d parts, and Models with Humanoids display their names. If the content of any such display is visible to players, and if another player generated that content, then the text needs to be filtered before it is displayed.
  
 
[[File:AlternateDisplay0.png|800px]]
 
[[File:AlternateDisplay0.png|800px]]
Line 199: Line 240:
  
 
==Random Words==
 
==Random Words==
Some games may find it useful to generate words from random characters that are then displayed to players. There is a chance that such generations could create inappropriate words. In such situations the displayed result of random words should be sent through a filter on the server.
+
Some games may find it useful to generate words from random characters that are then displayed to players. There is a chance that such generations could create inappropriate words. In such situations the displayed result of random words should be sent through a filter on the server. In such cases the user id of the player who is going to be viewing the words can be used in FilterStringAsync.  
  
For example, the following code sends a random word to players when they join the game (which will be displayed later). The generator makes sure that the word that is sent has been filtered first:
+
For example, the following code sends a random word to players when they join the game (which will be displayed later). The code will generate random words in a loop until it finds one that has not been altered by the filter. This example assumes a bit of set up. A place file is included here: [https://www.roblox.com/games/1076187052/Random-Text-Filtering-Place Working Example -Broadcast Text Filtering]
 +
 
 +
The generator makes sure that the word that is sent has been filtered first:
 
{{Code|=
 
{{Code|=
local Chat = game:GetService("Chat")
+
local TextService = game:GetService("TextService")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
  
 
local sendRandomWordEvent = ReplicatedStorage.RandomWordEvent
 
local sendRandomWordEvent = ReplicatedStorage.RandomWordEvent
 
+
local ALPHABET= "abcdefghijklmnopqrstuvwxyz"
local alphabet = "abcdefghijklmnopqrstuvwxyz"
+
local MIN_LENGTH = 3
local minLength = 3
+
local MAX_LENGTH = 8
local maxLength = 8
+
 
+
 
-- Function to generate a random word
 
-- Function to generate a random word
 
local function generateRandomWord()
 
local function generateRandomWord()
local length = math.random(minLength, maxLength)
+
local length = math.random(MIN_LENGTH, MAX_LENGTH)
 
local text = ""
 
local text = ""
 
for index = 1, length do
 
for index = 1, length do
 
local randomLetterIndex = math.random(1, string.len(alphabet))
 
local randomLetterIndex = math.random(1, string.len(alphabet))
text = text .. string.sub(alphabet, randomLetterIndex, randomLetterIndex)
+
text = text .. string.sub(ALPHABET, randomLetterIndex, randomLetterIndex)
 
end
 
end
 
return text
 
return text
 +
end
 +
 +
local function getTextObject(message, fromPlayerId)
 +
local textObject
 +
local success, errorMessage = pcall(function()
 +
textObject = TextService:FilterStringAsync(message, fromPlayerId)
 +
end)
 +
if success then
 +
return textObject
 +
elseif errorMessage then
 +
print("Error generating textObject")
 +
end
 +
return false
 +
end
 +
 +
local function getFilteredMessage(textObject, toPlayerId)
 +
local filteredMessage
 +
local success, errorMessage = pcall(function()
 +
filteredMessage = textObject:GetNonChatStringForUserAsync(toPlayerId)
 +
end)
 +
if success then
 +
return filteredMessage
 +
elseif errorMessage then
 +
print("Error filtering message",errorMessage)
 +
end
 +
return false
 
end
 
end
  
Line 231: Line 298:
 
filteredText = ""
 
filteredText = ""
 
text = generateRandomWord()
 
text = generateRandomWord()
local success, message = pcall(function()
+
-- filter the incoming message and send the filtered message
filteredText = Chat:FilterStringAsync(text, player, player)
+
local messageObject = getTextObject(text, player.UserId)
end)
+
filteredText = getFilteredMessage(messageObject, player.UserId)
 
until text == filteredText
 
until text == filteredText
 +
if text == filteredText then
 +
print("The message is",text,"The filtered message is",filteredText)
 +
end
 
-- Send the random word to the client
 
-- Send the random word to the client
 
sendRandomWordEvent:FireClient(player, text)
 
sendRandomWordEvent:FireClient(player, text)
 
end
 
end
 
 
game.Players.PlayerAdded:Connect(onPlayerJoined)
 
game.Players.PlayerAdded:Connect(onPlayerJoined)
 
}}
 
}}
Line 246: Line 315:
  
 
{{Code|=
 
{{Code|=
local Chat = game:GetService("Chat")
+
local TextService = game:GetService("TextService")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local HttpService = game:GetService("HttpService")
 
local HttpService = game:GetService("HttpService")
Line 261: Line 330:
 
if success then
 
if success then
 
nameTable = HttpService:JSONDecode(nameTableJSON)
 
nameTable = HttpService:JSONDecode(nameTableJSON)
 +
print("The nameTable is:",nameTable)
 
end
 
end
 
end
 
end
Line 268: Line 338:
 
local randomName = ""
 
local randomName = ""
 
local filteredName = ""
 
local filteredName = ""
 +
local filteredNameObject
 
repeat
 
repeat
 
randomName = nameTable[math.random(#nameTable)]
 
randomName = nameTable[math.random(#nameTable)]
filteredName = Chat:FilterStringAsync(randomName, player, player)
+
local success, errorMessage = pcall(function()
 +
filteredNameObject = TextService:FilterStringAsync(randomName, player.UserId)
 +
end)
 +
if success then
 +
print("Success creating filtered object")
 +
elseif errorMessage then
 +
print("Error creating filtered object")
 +
end
 +
local success, errorMessage = pcall(function()
 +
filteredName = filteredNameObject.GetNonChatStringForUserAsync(player.UserId)
 +
end)
 +
if success then
 +
print("Success creating filtered name")
 +
elseif errorMessage then
 +
print("Error creating filtered name")
 +
end
 
until randomName == filteredName
 
until randomName == filteredName
 
sendRandomName:FireClient(sendRandomName)
 
sendRandomName:FireClient(sendRandomName)
Line 284: Line 370:
  
 
{{Code|=
 
{{Code|=
local Chat = game:GetService("Chat")
+
local TextService = game:GetService("TextService")
 
local DataStoreService = game:GetService("DataStoreService")
 
local DataStoreService = game:GetService("DataStoreService")
  
Line 299: Line 385:
 
local petType = data.PetType
 
local petType = data.PetType
 
local filteredName = ""
 
local filteredName = ""
 +
local filteredNameObject
 
local success, message = pcall(function()
 
local success, message = pcall(function()
filteredName = Chat:FilterStringForBroadcast(petName, player)
+
filteredNameObject = TextService:FilterStringAsync(petName, player.UserId)
 
end)
 
end)
 
if success then
 
if success then
petCreator:MakePet(player, petType, filteredName)
+
local worked, errorMessage = pcall(function()
 +
filteredName = filteredNameObject:GetNonChatStringForBroadcastAsync()
 +
end)
 +
if worked then
 +
petCreator:MakePet(player, petType, filteredName)
 +
end
 
end
 
end
 
end
 
end

Latest revision as of 23:54, 2 October 2017

One of the most important ways to keep games safe and secure is to apply proper text filtering. Roblox has a text filter feature that not only prevents users from seeing profane or inappropriate messages, but also blocks personally identifiable information. Roblox has many young users who should be safeguarded against sharing or seeing certain content.

Because filtering is so crucial for a safe environment, Roblox actively moderates the content of games to make sure they meet certain standards. If a game is reported or automatically detected to not use filtering, that game will be shut down until the developer takes the proper steps to apply filtering.

How to filter text

Text filtering is done with the FilterStringAsync function of TextService. This function will take a string of text as input and the userId of the player who created the text and return a TextFilterResult object that can be used to distribute the filtered string.

FilterStringAsync should be called on the server as it will fail if called on the client.

FilterStringAsync

FilterStringAsync can be used to filter a string intended for a single user to view or for broadcasting a string globally. It takes two arguments: the string to filter and the id of the player who authored the text. In code this function would appear as:

local TextService = game:GetService("TextService")
local filteredTextResult = TextService:FilterStringAsync(text, fromPlayerId)

This function can be used to filter strings meant for specific users or all users in chat and non-chat situations. TextFilterResult, the object returned by the function, has three methods that can be called:GetChatForUserAsync, GetNonChatStringForBroadcastAsync, GetNonChatStringForUserAsync.

Note that FilterStringAsync and all of the TextFilterResult functions can fail on occasion as they make web calls internally, so they should always be wrapped in pcalls.

local TextService = game:GetService("TextService")
local filteredText = ""
local success, errorMessage = pcall(function()
	filteredTextResult = TextService:FilterStringAsync(text, fromPlayerId)
end)
if not success then
	warn("Error filtering text:", text, ":", errorMessage)
	-- Put code here to handle filter failure
end
If the pcall that contains the filter function fails, it is important not to continue using the text as intended. It is better to have an empty text field than to have unfiltered text.

Example

This example sets up a widget that allows a player to send a message to another. Such a widget needs at least two scripts: a local script to handle input and displaying messages, and a script to filter the messages on the server. Because this example has a player sending a message to another specific player, the GetChatForUserAsync function should be used.

This tutorial assumes that a GUI and a remote event has been set up already.

-Text Filtering Example

-- LocalScript
 
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local screen = playerGui:WaitForChild("MessageScreen")
local sendMessageEvent = ReplicatedStorage:WaitForChild("SendPrivateMessage")
 
-- GUI elements for send frame
local sendFrame = screen:WaitForChild("SendFrame")
local recipientField = sendFrame:WaitForChild("Recipient")
local writeMessageField = sendFrame:WaitForChild("Message")
local sendButton = sendFrame:WaitForChild("Send")
 
-- GUI elements for receive frame
local receiveFrame = screen:WaitForChild("ReceiveFrame")
local senderField = receiveFrame:WaitForChild("From")
local readMessageField = receiveFrame:WaitForChild("Message")
 
-- Called when send button is clicked
local function onSendClicked()
	-- Try to find the recipient. Only want to send message if recipient exists
	local recipient = Players:FindFirstChild(recipientField.Text)
	local message = writeMessageField.Text
	if recipient and message ~= "" then
		-- Send the message
		sendMessageEvent:FireServer(recipient, message)
		-- Clean up send frame
		recipientField.Text = ""
		writeMessageField.Text = ""
	end	
end
 
-- Called when send message event fires meaning this client got a message
local function onReceiveMessage(sender, message)
	-- Populate fields of receive frame with the sender and message
	senderField.Text = sender.Name
	readMessageField.Text = message
end
 
-- Bind event functions
sendButton.MouseButton1Click:Connect(onSendClicked)
sendMessageEvent.OnClientEvent:Connect(onReceiveMessage)


-- Server Script
 
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TextService = game:GetService("TextService")
 
local sendMessageEvent = ReplicatedStorage.SendPrivateMessage
 
local function getTextObject(message, fromPlayerId)
	local textObject
	local success, errorMessage = pcall(function()
		textObject = TextService:FilterStringAsync(message, fromPlayerId)
	end)
	if success then
		return textObject
	end
	return false
end
 
local function getFilteredMessage(textObject, toPlayerId)
	local filteredMessage
	local success, errorMessage = pcall(function()
		filteredMessage = textObject:GetChatForUserAsync(toPlayerId)
	end)
	if success then
		return filteredMessage
	end
	return false
end
 
-- Called when client sends a message
local function onSendMessage(sender, recipient, message)
	if message ~= "" then
		-- Filter the incoming message and send the filtered message
		local messageObject = getTextObject(message, sender.UserId)
 
		if messageObject then
			local filteredMessage = getFilteredMessage(messageObject, recipient.UserId)
			sendMessageEvent:FireClient(recipient, sender, message)
		end
	end
end
 
sendMessageEvent.OnServerEvent:Connect(onSendMessage)


Example

This example sets up a dialog that lets a player write a message on a sign. Since anyone in the server would be able to read the sign, even players who join the game after the writing player has left, the text has to be filtered with GetNonChatStringForBroadcastAsync. This example assumes that a place has been set up already with all the proper elements in place. A working place has been provided below.

Working Example -Broadcast Text Filtering

-- LocalScript
 
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local screen = playerGui:WaitForChild("MessageScreen")
 
-- GUI elements for dialog
local frame = screen:WaitForChild("Frame")
local messageInput = frame:WaitForChild("Message")
local sendButton = frame:WaitForChild("Send")
 
-- RemoteEvent to send text to server for filtering and display
local setSignText = ReplicatedStorage:WaitForChild("SetSignText")
 
-- Called when button is clicked
local function onClick()
	local message = messageInput.Text
	if message ~= "" then
		setSignText:FireServer(message)
		frame.Visible = false
	end
end
 
sendButton.MouseButton1Click:Connect(onClick)


-- Server Script
 
--local Players = game:GetService("Players")
local TextService = game:GetService("TextService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local sign = game.Workspace.Sign
local signTop = sign.Top
local signSurfaceGui = signTop.SurfaceGui
local signLabel = signSurfaceGui.SignLabel
 
local setSignText = ReplicatedStorage.SetSignText
 
local function getTextObject(message, fromPlayerId)
	local textObject
	local success, errorMessage = pcall(function()
		textObject = TextService:FilterStringAsync(message, fromPlayerId)
	end)
	if success then
		return textObject
	elseif errorMessage then
		print("Error generating TextFilterResult:", errorMessage)
	end
	return false
end
 
local function getFilteredMessage(textObject)
	local filteredMessage
	local success, errorMessage = pcall(function()
		filteredMessage = textObject:GetNonChatStringForBroadcastAsync()
	end)
	if success then
		return filteredMessage
	elseif errorMessage then
		print("Error filtering message:", errorMessage)
	end
	return false
end
 
-- Fired when client sends a request to write on the sign
local function onSetSignText(player, text)
	if text ~= "" then
		-- filter the incoming message and send the filtered message
		local messageObject = getTextObject(text, player.UserId)
		local filteredText = ""
		filteredText = getFilteredMessage(messageObject)
		signLabel.Text = filteredText
	end
end
 
setSignText.OnServerEvent:Connect(onSetSignText)

When to Filter Text

Any displayed text that a developer does not have explicit control over should be filtered. In general, this mainly refers to text that players have control over but there are a few other cases that are important to consider to make sure games are compliant with the Roblox filtering rules.

Player Input

Any text that a player writes that is to be displayed must be filtered, no matter how the text is input or displayed. The most common way to input text is through TextBoxes, but there can be any number of ways to get text input from a player, from a custom GUI with character buttons to interactive keyboard models in the 3d space.

AlternateInput0.png

AlternateInput1.png

Along with novel and unorthodox input methods, there are many ways of displaying text besides using TextLabels. For example, words can be spelled out with 3d parts, and Models with Humanoids display their names. If the content of any such display is visible to players, and if another player generated that content, then the text needs to be filtered before it is displayed.

AlternateDisplay0.png

AlternateDisplay1.png

Random Words

Some games may find it useful to generate words from random characters that are then displayed to players. There is a chance that such generations could create inappropriate words. In such situations the displayed result of random words should be sent through a filter on the server. In such cases the user id of the player who is going to be viewing the words can be used in FilterStringAsync.

For example, the following code sends a random word to players when they join the game (which will be displayed later). The code will generate random words in a loop until it finds one that has not been altered by the filter. This example assumes a bit of set up. A place file is included here: Working Example -Broadcast Text Filtering

The generator makes sure that the word that is sent has been filtered first:

local TextService = game:GetService("TextService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
 
local sendRandomWordEvent = ReplicatedStorage.RandomWordEvent
local ALPHABET= "abcdefghijklmnopqrstuvwxyz"
local MIN_LENGTH = 3
local MAX_LENGTH = 8
-- Function to generate a random word
local function generateRandomWord()
	local length = math.random(MIN_LENGTH, MAX_LENGTH)
	local text = ""
	for index = 1, length do
		local randomLetterIndex = math.random(1, string.len(alphabet))
		text = text .. string.sub(ALPHABET, randomLetterIndex, randomLetterIndex)
	end
	return text
end
 
local function getTextObject(message, fromPlayerId)
	local textObject
	local success, errorMessage = pcall(function()
		textObject = TextService:FilterStringAsync(message, fromPlayerId)
	end)
	if success then
		return textObject
	elseif errorMessage then
		print("Error generating textObject")
	end
	return false
end
 
local function getFilteredMessage(textObject, toPlayerId)
	local filteredMessage
	local success, errorMessage = pcall(function()
		filteredMessage = textObject:GetNonChatStringForUserAsync(toPlayerId)
	end)
	if success then
		return filteredMessage
	elseif errorMessage then
		print("Error filtering message",errorMessage)
	end
	return false
end
 
-- Called when player joins the game
local function onPlayerJoined(player)
	local text = ""
	local filteredText = ""
	-- Generate random words until one is created that passes the filter
	repeat
		filteredText = ""
		text = generateRandomWord()
		-- filter the incoming message and send the filtered message
		local messageObject = getTextObject(text, player.UserId)
		filteredText = getFilteredMessage(messageObject, player.UserId)
	until text == filteredText
	if text == filteredText then
		print("The message is",text,"The filtered message is",filteredText)
	end
	-- Send the random word to the client
	sendRandomWordEvent:FireClient(player, text)
end
game.Players.PlayerAdded:Connect(onPlayerJoined)

External Sources

Some games connect to external web servers. In some cases this is used to fetch content that is used to display information in game. If the content of the external site is not in full control of the developer and it is possible for a 3rd party to edit the information, then that content should be filtered if it is to be displayed.

local TextService = game:GetService("TextService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local HttpService = game:GetService("HttpService")
 
local sendRandomName = ReplicatedStorage.SendRandomName
local randomNameWebsiteAddress = "http://www.roblox.com/randomname"
local nameTable = nil
 
local function initializeNameTable()
	local nameTableJSON = nil
	local success, message = pcall(function()
		nameTableJSON = HttpService:GetAsync(randomNameWebsiteAddress)
	end)
	if success then
		nameTable = HttpService:JSONDecode(nameTableJSON)
		print("The nameTable is:",nameTable)
	end
end
 
local function onPlayerJoin(player)
	if nameTable then
		local randomName = ""
		local filteredName = ""
		local filteredNameObject
		repeat
			randomName = nameTable[math.random(#nameTable)]
			local success, errorMessage = pcall(function()
			filteredNameObject = TextService:FilterStringAsync(randomName, player.UserId)
			end)
			if success then
				print("Success creating filtered object")
			elseif errorMessage then
				print("Error creating filtered object")
			end
			local success, errorMessage = pcall(function()
			filteredName = filteredNameObject.GetNonChatStringForUserAsync(player.UserId)
			end)
			if success then
				print("Success creating filtered name")
			elseif errorMessage then
				print("Error creating filtered name")
			end
		until randomName == filteredName
		sendRandomName:FireClient(sendRandomName)
	end
end
 
initializeNameTable()
game.Players.PlayerAdded:Connect(onPlayerJoin)

Stored Text

Many games will store text using DataStores. For example, games may store a chat log, or a player’s pet name, etc. In such cases, if the text that is being stored needs to be filtered, it is recommended to filter when retrieving the text. This ensures that the most up-to-date version of the filter is being used.

local TextService = game:GetService("TextService")
local DataStoreService = game:GetService("DataStoreService")
 
local petData = nil
local petCreator = require(game.ServerStorage.PetCreator)
 
local function onPlayerJoin(player)
	local data = {}
	local success, message = pcall(function()
		data = petData:GetAsync(player.UserId)
	end)
	if success then
		local petName = data.Name
		local petType = data.PetType
		local filteredName = ""
		local filteredNameObject
		local success, message = pcall(function()
			filteredNameObject = TextService:FilterStringAsync(petName, player.UserId)
		end)
		if success then
			local worked, errorMessage = pcall(function()
				filteredName = filteredNameObject:GetNonChatStringForBroadcastAsync()
			end)
			if worked then
				petCreator:MakePet(player, petType, filteredName)
			end
		end
	end
end
 
local success, message = pcall(function()
	petData = DataStoreService:GetDataStore("PetData")
end)
if success then
	game.Players.PlayerAdded:Connect(onPlayerJoin)
end

Exception

The one exception to text filtering is when it comes to displaying text to a player that they wrote themselves, although there are still some considerations to keep in mind.

Filtering text through the chat filter functions takes a bit of time. For example, suppose a player types a message that they want to display. That text has to be sent to the server, filtered, and then sent back to the client. Each of these stages takes a bit of time. When run in a sequence like this, there can be a noticeable delay between when a message is typed and the filtered message is returned.

FilterPath0.png

When sending a message to other players, this process is necessary (as the other players need to see the filtered text). But the player who wrote the message should see their own message in the log right away. With this in mind, there is a special edge case that Roblox has built in for the convenience of chat. If a player enters text using a TextBox specifically, the resulting text does not have to be filtered for that player and can be displayed to that player right away.

FilterPath1.png

An important caveat of this exception is when retrieving stored messages. The automated checks that Roblox does to detect if filtering is being done correctly knows to ignore text that was typed into TextBoxes, but only in the same session that the TextBox was used. If a player’s text is saved and then is retrieved later when the player rejoins the game, that saved text needs to be filtered before it is displayed to anyone, including the player who wrote it.