Roblox GUI Maker logoRoblox GUI Maker
Unofficial third-party tool · Not affiliated with Roblox Corporation

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.

ServerScriptService/LeaderboardServer (Script)
lua
1-- Leaderboard - Server Script
2-- Place in: ServerScriptService (Script)
3
4local Players = game:GetService("Players")
5local DataStoreService = game:GetService("DataStoreService")
6local ReplicatedStorage = game:GetService("ReplicatedStorage")
7
8local leaderboard = DataStoreService:GetOrderedDataStore("GlobalLeaderboard")
9local refreshEvent = ReplicatedStorage:WaitForChild("LeaderboardRefresh")
10
11-- 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)
16end
17
18-- Read top 10 and push to clients
19local function refreshLeaderboard()
20 local success, pages = pcall(function()
21 return leaderboard:GetSortedAsync(false, 10)
22 end)
23 if not success then return end
24 local top = pages:GetCurrentPage()
25 refreshEvent:FireAllClients(top)
26end
27
28-- Auto-refresh every 60 seconds (respects rate limits)
29while true do
30 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.

StarterGui/LeaderboardClient (LocalScript)
lua
1-- Leaderboard - Client Script
2-- Place in: StarterGui (LocalScript)
3
4local Players = game:GetService("Players")
5local ReplicatedStorage = game:GetService("ReplicatedStorage")
6local TweenService = game:GetService("TweenService")
7
8local gui = script.Parent
9local rowsContainer = gui:WaitForChild("Rows")
10local refreshEvent = ReplicatedStorage:WaitForChild("LeaderboardRefresh")
11
12local function updateRows(top)
13 for i, row in ipairs(rowsContainer:GetChildren()) do
14 if row:IsA("Frame") then row:Destroy() end
15 end
16 for i, entry in ipairs(top) do
17 local userId = tonumber(entry.key)
18 local score = entry.value
19 local row = Instance.new("Frame")
20 row.Size = UDim2.new(1, 0, 0, 40)
21 row.BackgroundTransparency = 0.5
22 row.BackgroundColor3 = Color3.fromRGB(30, 30, 40)
23 row.Parent = rowsContainer
24
25 local rank = Instance.new("TextLabel")
26 rank.Text = "#" .. i
27 rank.Size = UDim2.new(0.15, 0, 1, 0)
28 rank.BackgroundTransparency = 1
29 rank.TextColor3 = Color3.fromRGB(255, 255, 255)
30 rank.Parent = row
31
32 local avatar = Instance.new("ImageLabel")
33 avatar.Image = Players:GetUserThumbnailAsync(
34 userId, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size100x100
35 )
36 avatar.Size = UDim2.new(0, 32, 0, 32)
37 avatar.Position = UDim2.new(0.2, 0, 0.1, 0)
38 avatar.Parent = row
39
40 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 = 1
45 nameLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
46 nameLabel.Parent = row
47
48 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 = 1
53 scoreLabel.TextColor3 = Color3.fromRGB(80, 200, 120)
54 scoreLabel.Parent = row
55
56 -- Fade in
57 row.BackgroundTransparency = 1
58 TweenService:Create(row, TweenInfo.new(0.2), { BackgroundTransparency = 0.5 }):Play()
59 end
60end
61
62refreshEvent.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.

Frequently asked questions