Roblox Leaderboard GUI Tutorial: SortedDataStore Ranking (2026)
2026-07-21 · Roblox GUI Maker Team
OrderedDataStore basics
OrderedDataStore is the standard way to rank players in Roblox. You write each player's score with SetAsync, and the store keeps them sorted automatically. When you read with GetSortedAsync, you get the top N entries in descending order. The key is the user's UserId, the value is their score. OrderedDataStore has rate limits - reads are limited to about 5 per second per server - so refresh on a timer, not on every change.
Write scores on the server
The server is the only place you should write leaderboard scores. When a player's score changes (they earn coins, win a match, level up), update their entry in OrderedDataStore. Use pcall so a failed write does not crash the game, and throttle updates - if a player's score changes many times per second, only write the final value after a short delay.
1-- Leaderboard - Server Script2-- Place in: ServerScriptService (Script)34local Players = game:GetService("Players")5local DataStoreService = game:GetService("DataStoreService")6local ReplicatedStorage = game:GetService("ReplicatedStorage")78local leaderboard = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")9local refreshEvent = ReplicatedStorage:WaitForChild("LeaderboardRefresh")1011-- Write a player's score (call this when their score changes)12local function writeScore(userId, score)13 pcall(function()14 leaderboard:SetAsync(tostring(userId), score)15 end)16end1718-- Read top 10 and push to clients19local function refreshLeaderboard()20 local success, pages = pcall(function()21 return leaderboard:GetSortedAsync(false, 10)22 end)23 if not success then return end24 local top = pages:GetCurrentPage()25 refreshEvent:FireAllClients(top)26end2728-- Auto-refresh every 60 seconds (respects rate limits)29while true do30 refreshLeaderboard()31 task.wait(60)32end
Display top 10 with avatars
The client renders the top 10 rows: rank, avatar, player name, and score. Fetch the player's avatar thumbnail with Players:GetUserThumbnailAsync using their UserId. Use UIListLayout to stack the rows, and fade each row in with a Tween when the leaderboard refreshes so the update feels alive. Show the player's own rank below the top 10 if they are not in it.
1-- Leaderboard - Client Script2-- Place in: StarterGui (LocalScript)34local Players = game:GetService("Players")5local ReplicatedStorage = game:GetService("ReplicatedStorage")6local TweenService = game:GetService("TweenService")78local gui = script.Parent9local rowsContainer = gui:WaitForChild("Rows")10local refreshEvent = ReplicatedStorage:WaitForChild("LeaderboardRefresh")1112local function updateRows(top)13 for i, row in ipairs(rowsContainer:GetChildren()) do14 if row:IsA("Frame") then row:Destroy() end15 end16 for i, entry in ipairs(top) do17 local userId = tonumber(entry.key)18 local score = entry.value19 local row = Instance.new("Frame")20 row.Size = UDim2.new(1, 0, 0, 40)21 row.BackgroundTransparency = 0.522 row.BackgroundColor3 = Color3.fromRGB(30, 30, 40)23 row.Parent = rowsContainer2425 local rank = Instance.new("TextLabel")26 rank.Text = "#" .. i27 rank.Size = UDim2.new(0.15, 0, 1, 0)28 rank.BackgroundTransparency = 129 rank.TextColor3 = Color3.fromRGB(255, 255, 255)30 rank.Parent = row3132 local avatar = Instance.new("ImageLabel")33 avatar.Image = Players:GetUserThumbnailAsync(34 userId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size100x10035 )36 avatar.Size = UDim2.new(0, 32, 0, 32)37 avatar.Position = UDim2.new(0.2, 0, 0.1, 0)38 avatar.Parent = row3940 local nameLabel = Instance.new("TextLabel")41 nameLabel.Text = Players:GetNameFromUserIdAsync(userId) or "Player"42 nameLabel.Size = UDim2.new(0.4, 0, 1, 0)43 nameLabel.Position = UDim2.new(0.35, 0, 0, 0)44 nameLabel.BackgroundTransparency = 145 nameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)46 nameLabel.Parent = row4748 local scoreLabel = Instance.new("TextLabel")49 scoreLabel.Text = tostring(score)50 scoreLabel.Size = UDim2.new(0.2, 0, 1, 0)51 scoreLabel.Position = UDim2.new(0.75, 0, 0, 0)52 scoreLabel.BackgroundTransparency = 153 scoreLabel.TextColor3 = Color3.fromRGB(80, 200, 120)54 scoreLabel.Parent = row5556 -- Fade in57 row.BackgroundTransparency = 158 TweenService:Create(row, TweenInfo.new(0.2), { BackgroundTransparency = 0.5 }):Play()59 end60end6162refreshEvent.OnClientEvent:Connect(updateRows)
Rate limits and auto-refresh
OrderedDataStore has read rate limits - about 5 reads per second per server. Refreshing the leaderboard on every score change would hit that limit fast. Instead, refresh on a timer (every 60 seconds is safe), and cache the top 10 so clients always have data to show even if a read fails. If your game has many servers, stagger the refresh times slightly so they do not all read at the same moment.
Show the player's own rank
Players want to know where they stand even if they are not in the top 10. After the top 10 rows, show the local player's rank and score. To get it, use OrderedDataStore:GetSortedAsync with a larger page size and scan for their UserId, or maintain a separate regular DataStore for each player's score and compute rank on the server. Show it as "Your rank: #147 (12,450)" below the top 10.