How to Make Leaderstats in Roblox (Leaderboard Guide)
Quick answer: Leaderstats in Roblox are made by creating a Folder named exactly "leaderstats" inside each Player when they join, then adding value objects like IntValue for each stat you want to track. Roblox detects that folder automatically and displays its values in the player list leaderboard — no UI code required.
To make leaderstats in Roblox, create a Folder named exactly "leaderstats" inside each player as they join, and put value objects such as an IntValue inside it for every stat you want to show. Roblox watches for that specific folder name and automatically renders its values in the player list at the top right of the screen — you write no leaderboard UI yourself.
The whole setup lives in one server Script (usually in ServerScriptService), takes about twenty lines, and scales from a simple Coins counter to a full stat system.
Why the name "leaderstats" matters
Roblox's default player list has a special hook built in: if it finds a child of a Player whose Name is exactly "leaderstats", it reads that folder's children and shows each one as a column, using the object's Name as the column header and its Value as the cell.
Three consequences follow from that design:
- The name must match exactly — lowercase, no spaces. A folder called
"LeaderStats"or"leaderStats"is silently ignored, and your leaderboard simply never appears. - The display order follows child order. Add Coins first and Kills second, and Coins appears as the leftmost column.
- Because the folder lives on the server, values replicate to everyone automatically. That's why leaderstats must be created in a server-side Script, not a LocalScript.
Creating leaderstats when a player joins
Hook into Players.PlayerAdded and build the structure per player:
local Players = game:GetService("Players")
local function onPlayerAdded(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = leaderstats
end
Players.PlayerAdded:Connect(onPlayerAdded)
Each value object becomes one column. IntValue holds whole numbers and covers most stats; use NumberValue for decimals, or StringValue/ObjectValue when a stat isn't numeric (for example, showing the player's current stage or equipped pet). The object's Name property is what players see, so "Coins" produces a "Coins" column.
If you also want to handle players who joined before your script ran (relevant in Studio play-solo timing), loop over Players:GetPlayers() once at startup and call onPlayerAdded for anyone already present.
A complete working example: coin pickups
Here is a full, runnable example. One script creates the leaderstats, and every part named "Coin" in workspace.Coins increments the collector's total when touched:
local Players = game:GetService("Players")
-- Set up leaderstats for every joining player
Players.PlayerAdded:Connect(function(player)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = 0
coins.Parent = leaderstats
end)
-- Coin pickups
for _, coin in workspace.Coins:GetChildren() do
local debounce = false
coin.Touched:Connect(function(hit)
if debounce then return end
local character = hit.Parent
local player = Players:GetPlayerFromCharacter(character)
if not player then return end
local leaderstats = player:FindFirstChild("leaderstats")
local coinsStat = leaderstats and leaderstats:FindFirstChild("Coins")
if not coinsStat then return end
debounce = true
coinsStat.Value += 1
coin.Transparency = 1
coin.CanTouch = false
task.wait(5) -- respawn delay
coin.Transparency = 0
coin.CanTouch = true
debounce = false
end)
end
Two details in this snippet matter beyond leaderstats. First, GetPlayerFromCharacter translates whatever touched the coin back into a player — otherwise a stray NPC limb or falling part would farm coins. Second, the debounce flag stops a single touch event from firing dozens of times as limbs brush past the part; the Touched event guide explains that pattern in depth, since nearly every touch-based mechanic needs it.
Updating stats anywhere else in your code
Because the value objects live under the player, any server script can reach them with a plain path lookup:
local function addCoins(player, amount)
local coins = player:FindFirstChild("leaderstats")
and player.leaderstats:FindFirstChild("Coins")
if coins then
coins.Value += amount
end
end
Prefer FindFirstChild over direct indexing during joins: there is a brief window after PlayerAdded fires where another script may not have finished building the folder yet, and indexing a missing instance throws "attempt to index nil".
Saving leaderstats with DataStores
The default leaderboard resets on every server restart, so real games persist their values with DataStoreService. The usual shape is: load saved stats inside a pcall when the player joins, write them back when they leave, and call game:BindToClose() so shutdowns don't drop the final save:
local DataStoreService = game:GetService("DataStoreService")
local coinStore = DataStoreService:GetDataStore("PlayerCoins")
Players.PlayerAdded:Connect(function(player)
local ok, saved = pcall(function()
return coinStore:GetAsync("uid_" .. player.UserId)
end)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
local coins = Instance.new("IntValue")
coins.Name = "Coins"
coins.Value = (ok and saved) or 0
coins.Parent = leaderstats
end)
Players.PlayerRemoving:Connect(function(player)
local coins = player:FindFirstChild("leaderstats")
and player.leaderstats:FindFirstChild("Coins")
if not coins then return end
pcall(function()
coinStore:SetAsync("uid_" .. player.UserId, coins.Value)
end)
end)
For production, prefer UpdateAsync over SetAsync (it reads-then-writes atomically), and wrap every call in pcall because DataStore requests fail routinely under throttling. The full walkthrough of keys, budgets, and BindToClose is in the Roblox DataStore guide.
Common leaderstats mistakes
These account for almost every "my leaderboard doesn't work" question:
- Renaming or misspelling the folder. It must be exactly
leaderstats. Renaming it later breaks the display even though the data still exists. - Creating the folder from a LocalScript. Client-created instances never replicate, so only the local player sees them — other players' boards stay empty, and the values aren't trustworthy. Always build leaderstats on the server.
- Changing
.Valueon the client. Same replication rule in reverse: a LocalScript can change what it sees locally, but the change vanishes for everyone else and gets overwritten by the next server update. - Indexing before the folder exists. Guard with
FindFirstChild, especially in scripts that run while players are still joining. - Adding new columns mid-session without handling existing players. If a player joined before your updated code ran, their folder lacks the new stat; re-check and add missing children rather than assuming the structure exists.
Do you ever need custom leaderboard UI?
Only when you outgrow the player list — global top-100 boards, team tables, or rich stat panels all require your own ScreenGui backed by ordered DataStores (GetOrderedDataStore). But the built-in leaderstats convention remains the correct foundation: it costs nothing, replicates reliably, and every experienced Roblox player looks at it instinctively.
Learn Luau by doing
Reading about leaderstats only gets you so far — the fastest way to internalize the PlayerAdded flow and event-driven updates is to build them yourself. Luablox walks you through these exact patterns step by step in its interactive lessons, with a reference library at /theory and hands-on challenges at /problems. Start with scripting for beginners if you want the full path from zero to your first working game systems.
Frequently asked questions
Why is my leaderstats folder not showing up in Roblox?
Check three things: the folder must be named exactly "leaderstats" (case-sensitive), it must be parented directly to the Player object, and it must be created by a server-side Script — folders made by a LocalScript never replicate and will not appear in the player list.
Can I make leaderstats with a LocalScript?
No. Instances created on the client exist only for that client, so a LocalScript-built leaderboard would be invisible to other players and would desync constantly. Create the leaderstats folder and value objects on the server, where changes replicate automatically.
What value objects can I use for leaderstats?
Any ValueBase descendant works: IntValue for whole numbers, NumberValue for decimals, StringValue for text like ranks, ObjectValue or BoolValue for non-numeric states. Each child of the leaderstats folder becomes one column, named after the object's Name property.
How do I keep leaderstats saved after the player leaves?
Use DataStoreService: load saved numbers inside a pcall during PlayerAdded and assign them to the IntValues, then save the current values in PlayerRemoving and game:BindToClose(). Use UpdateAsync for writes in production to avoid lost-update races.
Can I hide leaderstats from some players?
Not selectively through the built-in system — the player list shows the leaderstats folder to everyone. For per-player visibility you need custom UI: keep authoritative stats in server storage and render them with your own ScreenGui.
Learn core Roblox scripting and Luau programming concepts, including: how to make leaderstats roblox, leaderstats roblox, roblox leaderboard script, roblox player stats, roblox intvalue leaderstats.