Teams in Roblox: a spawn's TeamColor is not the spawn's colour
The Teams service already gives name colouring and separation in the player list with no script at all. What catches beginners out is two similarly named properties on SpawnLocation — and only one of them has anything to do with teams.
Produced with AI from the sources cited below, and checked by automated verification against those same sources. No person read this text before publication. Editorial Policy

Teams in Roblox are one of those features that deliver a lot before any code. Once the service is configured, the game already shows each player's name coloured by team and already separates the player list — with no script at all.
And they have a naming trap that catches almost every beginner: a property called TeamColor that is not the object's colour, sitting beside two that are.
The problem
The starting point: you can divide players into several teams using the Teams service. And the return is immediate — with the service configured and teams assigned, players are distinguished by the colour of their on-screen name and by their name in the default player list.
That is more than it sounds. Visual team identification is an interface problem that, done by hand, involves a label above the head, a colour per player and a list of your own. The service delivers all three for free.
The first thing to know is that no team comes configured by default. The service exists in the hierarchy, empty. Nothing happens until you create the objects.
Step by step
1. Create the teams. In the Explorer hierarchy, hover over the Teams object and insert a new Team object under the service.
2. Name it and choose the colour. On the selected object, set the Name and assign a unique, corresponding TeamColor. The name and the colour both appear in the game's player list.
The word "unique" in the documentation is not decoration: the TeamColor is what identifies the team elsewhere in the system — including at spawn points. Two teams with the same colour produce an ambiguity the engine resolves in a way you did not choose.
3. Repeat for the other teams.
4. Decide how players join. By default, Roblox automatically assigns a new player to the team with the fewest members. It is reasonable balancing and it solves most cases.
To control the assignment yourself, uncheck the AutoAssignable box on each team. Then the assignment becomes yours: from a script, changing the player's Team property, indexing the service by the team's name.
5. Lock the spawn points. SpawnLocation objects make players from different teams spawn in specific places, on joining or respawning.
And here is the default that has to be undone: by default a SpawnLocation is neutral, and any player can spawn on it. So each one has to be locked to the team that may occupy it.
That is two steps per spawn: unchecking the Neutral box in the properties, and setting the spawn's TeamColor to an existing team's TeamColor. If the team uses Lapis, the spawn uses Lapis too.
A note on the default balancing. Assigning to the team with fewest members is the right behaviour in most cases, and it is good that it is the default. It fails in three specific situations, and it is worth recognising them before switching it off by reflex.
The first is a match in progress: someone joining mid-match and landing on the losing team has a worse experience, and balancing by headcount does not know that.
The second is teams with different roles — a hunter against survivors, attackers against defenders. There the balance is not numeric, and assigning evenly is assigning wrongly.
The third is a group of friends: three people joining together go to different teams, because each is evaluated alone at the moment they join.
Outside those three, the default solves it — and solves it with no code.
The artifact
The script below has two parts, and the second pays off fastest.
The first is an assignment of your own with rebalancing — useful when the default does not serve: a match in progress, teams with different roles, friends who need to land together. It depends on AutoAssignable being unchecked; with it on, Roblox assigns first and the script becomes a second opinion that arrives late.
The second is a spawn check that runs in Studio and points at the three defects that raise no error at all: a neutral spawn in a game with teams, a spawn whose TeamColor matches no team, and a team with no spawn.
That third case is the cruellest: the team exists, players are assigned to it, and they spawn nowhere in particular — because there is no spawn locked to that colour.
It is also worth noting what the service does not do, so as not to expect too much of it. It identifies and separates visually; it prevents nothing. A player on one team can attack their own team, walk into the enemy base and take the other side's item — unless your code says otherwise. Everything that is a rule reads the Team property and decides; the service only guarantees that property exists and appears on screen.
Common mistakes
The champion mistake is in a note the documentation makes a point of leaving at the end, and it deserves reading word by word: SpawnLocation.TeamColor is different from SpawnLocation.BrickColor and SpawnLocation.Color, which represent the object's visual colour and have no relation to team functionality.
That is three properties, two of them cosmetic, with names suggesting the same thing. And the resulting behaviour is as confusing as it gets: you paint the spawn blue, the game shows a blue spawn, and it still does not belong to the blue team.
The reverse happens too and is worse: you set the right TeamColor, the spawn works, and it stays grey on screen. Then somebody on the team "fixes" the colour through BrickColor, and now the spawn looks right and works right — but nobody knows which of the two actions solved it.
The rule that avoids all of that: TeamColor decides who spawns; BrickColor and Color decide what is seen. Set both, on purpose, knowing they do different things.
The second mistake is forgetting to uncheck Neutral. A neutral spawn in a game with teams works — for everyone. Red team players spawn at the blue spawn, because it accepts anyone. The symptom is chaotic and intermittent, because it depends on which spawn the engine picked at that instant.
The third is leaving AutoAssignable on and assigning by script at the same time. Both work, in the wrong order: Roblox assigns the player on joining, your script assigns afterwards, and the player visibly changes team in the first second of play. It is not a bug that breaks anything, and it is ugly enough for the player to notice.
The fourth is reusing the same colour on two teams. Since TeamColor is the identifier the spawns use, two teams with the same colour make it impossible to say which of them a spawn belongs to.
Creating a team from a script mid-match. It works, and it is almost always a sign of wrong design. The map's SpawnLocations point at team colours that have to exist beforehand; a team created afterwards has nowhere to spawn, and the players assigned to it land at the default spawn. Teams are map structure, not match state — what changes during play is who is on which, not which exist.
One further care applies to the team's name, because it is used in two places with different requirements: it appears in the player list, where it has to be readable and short, and it is the key used in script assignment, where it has to be exact. Renaming a team after the game is live breaks every assignment written with the old name — and the error does not appear until somebody joins and receives no team at all.
Next step
With the teams working, the highest-return exercise is the spawn check — and it is worth doing before any test with people.
Run the artifact's second part in Studio and read the warnings. The list usually has at least one item in any map that has grown over time: an old spawn left neutral, or a new team nobody created a spawn point for.
After that, it is worth thinking about what a team means beyond the colour. The service delivers visual identification and separation in the list; everything that is a rule — friendly fire, a door only one team opens, a scoreboard by team — is your code, and all of it reads the same Team property on the player.
And, if your game has matches, the next question is what happens when somebody leaves mid-match. Roblox's automatic balancing only acts on joining; keeping the teams balanced during the match is a design decision, and that is why the artifact brings a rebalance that moves whoever joined last, rather than whoever has been playing longest.
Sources
- Implement teams | Documentation - Roblox Creator Hub — checked on
Ready-to-use artifact
-- ServerScriptService: team assignment with balancing of your own.
--
-- The Roblox default puts a new player on the team with the FEWEST
-- members, and that solves most cases. This script exists for when it
-- does not: a match in progress, teams with different roles, friends
-- who need to land together.
--
-- For it to work, EACH team's AutoAssignable has to be UNCHECKED. With
-- it on, Roblox assigns first and this script becomes a second opinion
-- that arrives late.
local Teams = game:GetService("Teams")
local Players = game:GetService("Players")
local Assignment = {}
local function count(team)
return #team:GetPlayers()
end
-- The team with the fewest people. On a tie: the first on the list, which
-- is stable.
local function smallest()
local chosen, fewest = nil, math.huge
for _, team in Teams:GetTeams() do
local n = count(team)
if n < fewest then
chosen, fewest = team, n
end
end
return chosen
end
function Assignment.join(player)
local team = smallest()
if not team then
warn("no Team configured in Teams: the player ends up with none")
return
end
player.Team = team
player.Neutral = false
end
-- Rebalances when somebody leaves and the gap grows. It moves the player
-- who joined the full team LAST -- that is the least disruptive to whoever
-- is already playing.
function Assignment.rebalance(maxGap)
local teams = Teams:GetTeams()
if #teams < 2 then
return
end
table.sort(teams, function(a, b) return count(a) > count(b) end)
local full, empty = teams[1], teams[#teams]
if count(full) - count(empty) <= (maxGap or 1) then
return
end
local candidates = full:GetPlayers()
local last = candidates[#candidates]
if last then
last.Team = empty
last:LoadCharacter()
end
end
-- ── SPAWN CHECK, in Studio ──────────────────────────────────────────
-- Runs once and points at the three defects that raise no error:
-- 1. a neutral spawn in a game with teams
-- 2. a spawn whose TeamColor belongs to no team
-- 3. a team with no spawn at all
function Assignment.checkSpawns()
local colours = {}
for _, team in Teams:GetTeams() do
colours[team.TeamColor.Name] = team.Name
end
local served = {}
for _, obj in workspace:GetDescendants() do
if obj:IsA("SpawnLocation") then
if obj.Neutral then
warn("NEUTRAL spawn: " .. obj:GetFullName())
elseif not colours[obj.TeamColor.Name] then
-- the silent case: a TeamColor matching no team
warn("spawn with a teamless TeamColor: " .. obj:GetFullName()
.. " (" .. obj.TeamColor.Name .. ")")
else
served[colours[obj.TeamColor.Name]] = true
end
end
end
for _, team in Teams:GetTeams() do
if not served[team.Name] then
warn("team WITH NO SPAWN: " .. team.Name)
end
end
end
Players.PlayerAdded:Connect(Assignment.join)
Players.PlayerRemoving:Connect(function()
task.defer(Assignment.rebalance, 1)
end)
return Assignment


