[Update] February 13, 2026
TL;DR
- Today we’re launching a new API method that will let you group and teleport players together who are of a similar age and can chat:
TextChatService:GetChatGroupsAsync. Next week, we’ll launch another method for Voice Chat Service:VoiceChatService:GetChatGroupsAsync.- These APIs take a list of Player instances as an input and returns a sorted array of arrays of chat group IDs players belong to. Matching Group IDs represent the ability to communicate based on players’ age-check status and settings.
- To use these API methods you must opt-in through Experience Settings > Communication in Studio.
- Note: Before you can use these methods, you must agree to using these APIs responsibly and comply with our Terms of Use.
- These server-side methods return group IDs that are unique for the current universe. Since the chat groups for a user returned by the API could change over time, you should check each user’s chat groups every time they leave and re-enter your experience.
Hi Creators,
As we mentioned in our latest roadmap update, we’re launching two new API methods that will let you group and teleport players together who are of a similar age and can chat. We know how important it is to keep users who can chat together, especially for roleplaying and social games. These new methods are:
TextChatService:GetChatGroupsAsync: Returns matching groups for players who can synchronously text chat together. This is now available. Read our documentation here.VoiceChatService:GetChatGroupsAsync: Returns matching groups for players eligible to use Voice Chat together. This will be available next week; we’ll update this post when it is.
How it works:
- You must first enable these APIs for your experience by going to Experience Settings > Communication and toggling on “Enable chat & voice groups APIs”.
- Note: By doing so you must agree to using these APIs responsibly and comply with our Terms of Use.
- Keep in mind you can only use API responses for their intended use. Storing, aggregating, or cross-referencing data returned from this API such as chat groups to build profiles or infer personal details about users is not permitted.
- For both API methods, you provide a list of players as an input, and the API returns multiple arrays. Each array represents a group of users who can text or voice chat with each other based on their age group and chat settings.
These server-side methods return group IDs that are unique for the current universe. Since the chat groups for a user returned by the API could change over time, you should check each user’s chat groups every time they leave and re-enter your experience.
What you can use these APIs for
Queue-Based Matchmaking
If you have a lobby or queue, you can use these APIs to ensure that the group of players you are about to teleport into a match are matched and put on the same team.
The code snippet below waits for players to come to the server and the moment there are 4 players that can make two teams where all the players in each team can chat with their team mates (2vs2) it teleports them.
Queue-Based Matchmaking - Code Snippet
-- Server Script
local Players = game:GetService("Players")
local TextChatService = game:GetService("TextChatService")
local TeleportService = game:GetService("TeleportService")
local TARGET_PLACE_ID = 1234567890
local MATCH_SIZE = 4
local Queue: { Player } = {}
local function removeFromQueue(playersToRemove)
local remove = {}
for _, player in ipairs(playersToRemove) do
remove[player] = true
end
local newQueue = {}
for _, player in ipairs(Queue) do
if not remove[player] then
table.insert(newQueue, player)
end
end
Queue = newQueue
end
-- Returns true if two groupId arrays intersect
local function sharesChatGroup(groupsA, groupsB)
local lookup = {}
for _, groupId in ipairs(groupsA) do
lookup[groupId] = true
end
for _, groupId in ipairs(groupsB) do
if lookup[groupId] then
return true
end
end
return false
end
local function tryCreateMatch()
if #Queue < MATCH_SIZE then
return
end
-- Fetch chat groups for queued players
local success, chatGroupsByIndex = pcall(function()
return TextChatService:GetChatGroupsAsync(Queue)
end)
if not success or not chatGroupsByIndex then
warn("Failed to retrieve chat groups")
return
end
-- chatGroupsByIndex[i] corresponds to Queue[i]
-- Each entry is an array of groupIds for that player
-- Find all compatible pairs (indices into Queue)
local compatiblePairs = {}
for i = 1, #Queue - 1 do
for j = i + 1, #Queue do
if sharesChatGroup(chatGroupsByIndex[i], chatGroupsByIndex[j]) then
table.insert(compatiblePairs, { i, j })
end
end
end
-- Combine pairs into a 2v2
for i = 1, #compatiblePairs - 1 do
for j = i + 1, #compatiblePairs do
local pairA = compatiblePairs[i]
local pairB = compatiblePairs[j]
-- Ensure all indices are unique
local used = {
[pairA[1]] = true,
[pairA[2]] = true,
}
if not used[pairB[1]] and not used[pairB[2]] then
local matchPlayers = {
Queue[pairA[1]],
Queue[pairA[2]],
Queue[pairB[1]],
Queue[pairB[2]],
}
removeFromQueue(matchPlayers)
-- Reserve private server
local accessCode
local ok, err = pcall(function()
accessCode = TeleportService:ReserveServer(TARGET_PLACE_ID)
end)
if not ok then
warn("Failed to reserve server:", err)
return
end
local teleportOk, teleportErr = pcall(function()
TeleportService:TeleportToPrivateServer(
TARGET_PLACE_ID,
accessCode,
matchPlayers
)
end)
if not teleportOk then
warn("Teleport failed:", teleportErr)
end
return
end
end
end
end
local function onPlayerAdded(player)
table.insert(Queue, player)
tryCreateMatch()
end
local function onPlayerRemoving(player)
removeFromQueue({ player })
end
Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
Custom Server Lists
If your experience features a custom server browser, you can now rank or filter those servers based on which ones contain the most users the local player is eligible to chat with.
- The code snippet below checks a database of servers and the current amount of compatible players in all chat groups in each server.
- It counts the number of users that the person can chat with in each server, and then sorts the servers based on that number.
Custom Server List - Code Snippet
-- Server Script
local TextChatService = game:GetService("TextChatService")
local Players = game:GetService("Players")
--[[
Stubbed universe-wide server state.
In practice, this data could be stored and updated using MemoryStore
or another backend system.
Example shape:
ActiveServers = {
{
serverId = "abc",
playing = 18,
maxPlayers = 24,
groupUserCounts = {
groupA = 12,
groupB = 5
}
},
{
serverId = "def",
playing = 12,
maxPlayers = 24,
groupUserCounts = {
groupB = 8
}
}
}
]]
local ActiveServers = {}
-- Returns servers ranked by chat compatibility with the player
local function getRankedServersForPlayer(player)
local success, chatGroups = pcall(function()
return TextChatService:GetChatGroupsAsync({ player })
end)
if not success or not chatGroups then
warn("Failed to retrieve chat groups for player:", player.UserId)
return {}
end
-- Collect all chat group IDs the player is eligible for
local playerGroupIds = {}
for _, groupSet in ipairs(chatGroups[1]) do
for _, groupId in ipairs(groupSet) do
playerGroupIds[groupId] = true
end
end
-- Score each server based on compatible users
local rankedServers = {}
for _, server in ipairs(ActiveServers) do
local compatibleUserCount = 0
for groupId, userCount in pairs(server.groupUserCounts) do
if playerGroupIds[groupId] then
compatibleUserCount += userCount
end
end
-- Optional: filter out servers with zero compatible users
if compatibleUserCount > 0 then
table.insert(rankedServers, {
serverId = server.serverId,
playing = server.playing,
maxPlayers = server.maxPlayers,
compatibleUsers = compatibleUserCount,
})
end
end
-- Rank servers: most compatible users first
table.sort(rankedServers, function(a, b)
return a.compatibleUsers > b.compatibleUsers
end)
return rankedServers
end
-- TODO: Render servers
Reserved Servers
Automatically group users who have completed an age check into servers with other users who have completed an age check.
The snippet below is a script that gets users who join an experience, finds a reserved server with the most people that they can chat with, then teleports them there.
Reserved Server - Code Snippet
-- Server Script
local TextChatService = game:GetService("TextChatService")
local TeleportService = game:GetService("TeleportService")
local Players = game:GetService("Players")
local TARGET_PLACE_ID = 1234567890 -- Destination place ID
--[[
Stubbed universe-wide server state.
In practice, this data could be stored and updated using MemoryStore
or another backend system.
Example shape:
ActiveServers = {
{
serverId = "abc",
accessCode = "reserved-server-code",
groupUserCounts = {
groupA = 12,
groupB = 5
}
},
{
serverId = "def",
accessCode = "reserved-server-code",
groupUserCounts = {
groupB = 8
}
}
}
]]
local ActiveServers = {}
local function teleportPlayerToBestServer(player)
local success, chatGroups = pcall(function()
return TextChatService:GetChatGroupsAsync({ player })
end)
if not success or not chatGroups then
warn("Failed to retrieve chat groups for player:", player.UserId)
return
end
-- Collect all chat group IDs the player is eligible for
local playerGroupIds = {}
for _, group in ipairs(chatGroups) do
for _, groupId in ipairs(group) do
playerGroupIds[groupId] = true
end
end
local bestServer = nil
local bestUserCount = 0
-- Choose the server with the highest number of compatible users
for _, server in ipairs(ActiveServers) do
local compatibleUserCount = 0
for groupId, userCount in pairs(server.groupUserCounts) do
if playerGroupIds[groupId] then
compatibleUserCount += userCount
end
end
if compatibleUserCount > bestUserCount then
bestUserCount = compatibleUserCount
bestServer = server
end
end
if not bestServer then
warn("No suitable server found for player:", player.UserId)
return
end
local ok, err = pcall(function()
TeleportService:TeleportToPrivateServer(
TARGET_PLACE_ID,
bestServer.accessCode,
{ player }
)
end)
if not ok then
warn("Teleport failed:", err)
end
end
Players.PlayerAdded:Connect(teleportPlayerToBestServer)
What’s Next
These APIs are just the first step in what we’re doing to keep experiences fun, engaging, and connected. Some other upcoming updates will include:
- Experience Chat for Trusted Connections - Next Week: Trusted Connections will be able to see each other’s messages in Experience Text and Voice Chat regardless of age group.
- Preset Chat Messages - Early 2026: Users will be able to use preset chat messages across all age groups to coordinate gameplay.
- Global Chat - Mid 2026: Users will be able to chat across servers with a new text chat channel.
Tomorrow, we’ll publish our next progress update on our communication roadmap; be sure to check back in!
FAQs
1. What happens if I try to use these APIs without toggling on the setting in Experience Settings?
- The methods will return an error. You must explicitly consent to using these APIs responsibly by enabling “Enable Text & Voice Chat group APIs” under Experience Settings > Communication.
2. Will these APIs return the age of users?
- No. The APIs only return groups of users who are eligible to chat together based on their age group and privacy settings. You will not receive specific age data.
3. What happens if I store any information returned by either API after a user has left the experience?
- The data and chat groups for a user can change over time for a number of different reasons; to make sure you have the latest groups a user can chat with, you should call this method anytime a user leaves then rejoins the experience.
- Keep in mind you must comply with Roblox’s Terms of Use and you can only use API responses for their intended use. Storing, aggregating, or cross-referencing data returned from this API such as chat groups to build profiles or infer personal details about users is not permitted.

