How to make a simple global leaderboard

Edit: May 3rd, 2023

Roblox has recently announced their plan to remove support for PointsService on June 30th. Please be advised that this tutorial has not been updated to remove PointsService yet. It’s on my to do list. In the mean time, see the FAQ “Why only pointsService? How can I use it without pointsService?”

Global leaderboards are great for adding competition to a game. Players want to become the best of them all, and will work hard to compete to become the best. If you make your leaderboard look nice, players will do anything to be better than the rest.


Taken from my game, The Tower Tycoon
Just look how hard people worked just to have a number and their name displayed on a virtual board.


Before You Begin

For this tutorial, it is recommended you know the basics of manipulating parts in studio and making a surface gui .

This tutorial is based off of pointsService for simplicity sake. This can be changed. For more info, check the FAQ at the bottom once you have read through. For now, don’t worry about it.

I don't want to read the tutorial, just give me the leaderboard

Don’t want to make the physical leaderboard and just want to code it? Skip to the coding section (has download for basic leaderboard without script). Don’t want to do that either? Download for the whole thing is at the end. (FAQ is available there too)


Let’s Begin!

With that out of the way, welcome. Today I will be showing you how to make a nice global leaderboard to let your players compete to become the best.


Surface GUI Designing

  1. The first step to making a global leaderboard is to make the physical part it will be on. It can be any size, and you can add cylinders to create a rounded leaderboard, change colors, add transparency, and more. For this tutorial however, we will use a simple 6x9x1 part. It is important to make the leaderboard look nice and appealing, but this can be done later. For now, let’s keep it simple.
    image

  2. The second step is we want to make a Surface GUI to hold our leaderboard. You can do this by clicking the + icon on the part, and typing in Surface GUI.
    image

  3. Now that we have a Surface GUI, we want to add a scrolling frame to hold the important stuff. Name it “ScrollingFrame”, so we can reference it later. Set the size to give a bit of space at the top, as we want to have a header at the top. Style it to your liking, such as color, ect. I recommend setting scrollBarThickness to 20.
    image image

  4. Add a UIListLayout to the Scrolling Frame. Name it “UI”, and set SortOrder to LayoutOrder.image image

  5. There’s one more thing we need to do before we get into the code; We need a frame to display the information. Make a sample frame such as the below one (Called “Sample”), and have imageLabel named “Image”, and 3 textLabels named “Place”, “PName”, and “Value”. Parent “Place” to the Image, as it will make it easier to scale (Scale & Pos = {0.5, 0},{0.5, 0}).
    image image

  6. We now have a Surface GUI that’s ready to be scripted! Add a script (not Local Script) to the surface gui. Parent the frame “Sample”.
    image


It’s Coding Time!

Time to code! At this point, you should have a leaderboard like this: Leaderboard Without Script.rbxl (21.7 KB).

We need to define all of our variables first, so we can easily reference items. (You can make your variable names longer, but I prefer shorter names. If you can remember them, use them.)

local sg = script.Parent --Surface GUI
local sample = script:WaitForChild("Sample") --Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") --The scrolling frame
local ui = sf:WaitForChild("UI") --The UI list layout

Nest, we need to choose the name for our global Ordered Data Store and set it to a variable. This will allow us to use Roblox to sort the data for us, without having to worry about thousands of entries to sort through. Note: Changing the key will change where roblox fetches our data from. This is useful for having multiple leaderboards, or just resetting data.

local dataStoreService = game:GetService("DataStoreService")
--The data store service
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard")
--Get the data store with key "Leaderboard"
--Want to make different leaderboards?
--Change this key to a different string ("Leaderboard2", ect.), and you can have multiple leaderboards!
I want daily leaderboards!

Not all people want all-time leaderboards. To acheive daily leaderboards, simply create a unique key for each day using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day

Now, start a loop to update data and fetch data.

wait(5)
while true do
--will add wait(120) at the end
--loop that runs every 2 minutes	

Then, we need to update the data. We loops through all the players, and update their data. In this tutorial, we use player points as the data to store (How To Award Player Points). However, you can easily reroute this to feature and value by defining w as something else.

	for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
		if plr.UserId>0 then--Prevent errors
            local ps = game:GetService("PointsService")--PointsService
			local w = ps:GetGamePointBalance(plr.UserId)--Get point balance
			if w then
				pcall(function()
                --Wrap in a pcall so if Roblox is down, it won't error and break.
					dataStore:UpdateAsync(plr.UserId,function(oldVal)
                        --Set new value
						return tonumber(w)
					end)
				end)
			end
		end
	end

Now, we need to fetch our data. We can do this by using our dataStore we defined earlier.

    local smallestFirst = false--false = 2 before 1, true = 1 before 2
    local numberToShow = 100--Any number between 1-100, how many will be shown
    local minValue = 1--Any numbers lower than this will be excluded
    local maxValue = 10e30--(10^30), any numbers higher than this will be excluded
    local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
    --Get data
    local top = pages:GetCurrentPage()--Get the first page

Yay! We now have our data, nice and organized with it’s key (userid), and value (points). But wait, we don’t have the player’s image or username! We need to fetch those before displaying our data.

   local data = {}--Store new data
   for a,b in ipairs(top) do--Loop through data
      local userid = b.key--User id
      local points = b.value--Points
      local username = "[Failed To Load]"--If it fails, we let them know
      local s,e = pcall(function()
         username = game.Players:GetNameFromUserIdAsync(userid)--Get username
      end)
      if not s then--Something went wrong
           warn("Error getting name for "..userid..". Error: "..e)
      end
      local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
      --Make a image of them
      table.insert(data,{username,points,image})--Put new data in new table
   end

Our data is all organized and tidy! Now, we want to take this tidy data and display it! (username, points, image)

	ui.Parent = script
	sf:ClearAllChildren()--Remove old frames
	ui.Parent = sf
	for number,d in pairs(data) do--Loop through our new data
		local name = d[1]
		local val = d[2]
		local image = d[3]
		local color = Color3.new(1,1,1)--Default color
		if number == 1 then
			color = Color3.new(1,1,0)--1st place color
		elseif number == 2 then
			color = Color3.new(0.9,0.9,0.9)--2nd place color
		elseif number == 3 then
			color = Color3.fromRGB(166, 112, 0)--3rd place color
		end
		local new = sample:Clone()--Make a clone of the sample frame
		new.Name = name--Set name for better recognition and debugging
                new.LayoutOrder = number--UIListLayout uses this to sort in the correct order
		new.Image.Image = image--Set the image
		new.Image.Place.Text = number--Set the place
		new.Image.Place.TextColor3 = color--Set the place color (Gold = 1st)
		new.PName.Text = name--Set the username
		new.Value.Text = val--Set the amount of points
		new.Value.TextColor3 = color--Set the place color (Gold = 1st)
		new.PName.TextColor3 = color--Set the place color (Gold = 1st)
		new.Parent = sf--Parent to scrolling frame
	end
	wait()
	sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
	--Give enough room for the frames to sit in
       wait(120)
end--End the while loop

Our full code masterpiece!

local sample = script:WaitForChild("Sample") --Our Sample frame
local sf = sg:WaitForChild("ScrollingFrame") --The scrolling frame
local ui = sf:WaitForChild("UI") --The UI list layout

local dataStoreService = game:GetService("DataStoreService")
--The data store service
local dataStore = dataStoreService:GetOrderedDataStore("Leaderboard")
--Get the data store with key "Leaderboard"

wait(10)
while true do
	for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
		if plr.UserId>0 then--Prevent errors
			local ps = game:GetService("PointsService")--PointsService
			local w = ps:GetGamePointBalance(plr.UserId)--Get point balance
			if w then
				pcall(function()
				--Wrap in a pcall so if Roblox is down, it won't error and break.
					dataStore:UpdateAsync(plr.UserId,function(oldVal)
				        --Set new value
						return tonumber(w)
					end)
				end)
			end
		end
	end    
	local smallestFirst = false--false = 2 before 1, true = 1 before 2
    local numberToShow = 100--Any number between 1-100, how many will be shown
    local minValue = 1--Any numbers lower than this will be excluded
    local maxValue = 10e30--(10^30), any numbers higher than this will be excluded
    local pages = dataStore:GetSortedAsync(smallestFirst, numberToShow, minValue, maxValue)
    --Get data
    local top = pages:GetCurrentPage()--Get the first page
	local data = {}--Store new data
	for a,b in ipairs(top) do--Loop through data
		local userid = b.key--User id
		local points = b.value--Points
		local username = "[Failed To Load]"--If it fails, we let them know
		local s,e = pcall(function()
		 username = game.Players:GetNameFromUserIdAsync(userid)--Get username
		end)
		if not s then--Something went wrong
		   warn("Error getting name for "..userid..". Error: "..e)
		end
		local image = game.Players:GetUserThumbnailAsync(userid, Enum.ThumbnailType.HeadShot, Enum.ThumbnailSize.Size150x150)
		--Make a image of them
		table.insert(data,{username,points,image})--Put new data in new table
	end
	ui.Parent = script
	sf:ClearAllChildren()--Remove old frames
	ui.Parent = sf
	for number,d in pairs(data) do--Loop through our new data
		local name = d[1]
		local val = d[2]
		local image = d[3]
		local color = Color3.new(1,1,1)--Default color
		if number == 1 then
			color = Color3.new(1,1,0)--1st place color
		elseif number == 2 then
			color = Color3.new(0.9,0.9,0.9)--2nd place color
		elseif number == 3 then
			color = Color3.fromRGB(166, 112, 0)--3rd place color
		end
		local new = sample:Clone()--Make a clone of the sample frame
		new.Name = name--Set name for better recognition and debugging
                new.LayoutOrder = number--UIListLayout uses this to sort in the correct order
		new.Image.Image = image--Set the image
		new.Image.Place.Text = number--Set the place
		new.Image.Place.TextColor3 = color--Set the place color (Gold = 1st)
		new.PName.Text = name--Set the username
		new.Value.Text = val--Set the amount of points
		new.Value.TextColor3 = color--Set the place color (Gold = 1st)
		new.PName.TextColor3 = color--Set the place color (Gold = 1st)
		new.Parent = sf--Parent to scrolling frame
	end
	wait()
	sf.CanvasSize = UDim2.new(0,0,0,ui.AbsoluteContentSize.Y)
	--Give enough room for the frames to sit in
        wait(120)
end
I want the top bar from the first photo!

For those wanting a top bar, you can duplicate the sample frame and put it outside of the scrolling frame. Remember, you can mess with colors and text, and make a really cool leaderboard.


FAQ (Frequently Asked Questions)

How do I make multiple leaderboards?

Duplicate the leaderboard, and then refer to above to set a different scope:

How do I make a daily, monthly, or yearly leaderboard?

To make leaderboards “Reset” we can simply change where we save and get the data from. os.time() gives us the number of seconds since the unix equinox. os.date(time) convertes those seconds into a date.

You will need to store each leaderboard in a separate key.

To acheive daily leaderboards, simply create a unique key for each day using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day

To acheive monthly leaderboards, simply create a unique key for each month using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"]
--Unique key for each month

To acheive yearly leaderboards, simply create a unique key for each year using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"]
--Unique key for each year
How do I "Reset" the data, not just the data fetched, for every day / month / year?
Why only pointsService? How can I use it without pointsService?

I used pointsService in this tutorial for simplicity sake. If you want to pull the value from somewhere, you can easily reroute the value by defining w as something else.

In the example below, we set w to the player’s cash leaderstat. plr is the player, so reroute w however you like.

	for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
		if plr.UserId>0 then--Prevent errors
			local w = plr.leaderstats.Cash.Value--Get value
			if w then
				pcall(function()
                --Wrap in a pcall so if Roblox is down, it won't error and break.
					dataStore:UpdateAsync(plr.UserId,function(oldVal)
                        --Set new value
						return tonumber(w)
					end)
				end)
			end
		end
	end
I can't scroll on my leaderboard! How do I fix it?

If you put a part inside of the leaderboard, it might prevent you from scrolling. To prevent this, move the part that has the Surface GUI FORWARD. This way, the scrolling frame is over the part, which originally was blocking the scrolling frame.


Thanks for reading!

Thanks for reading! (and hopefully following along, thought it’s totally fine if you just wanted the leaderboard) If there is anything you are confused about, or think I missed, let me know. Feel free to PM me, or reply below. I can help solve your problem!


Downloads

Here all all the respective downloads for each of the stages. Feel free to use them as you please.

Download Notice

The most up to date code is above, though they should all be up to date. If you find any that are not, please notify me. Thanks!

Model without code: Leaderboard Without Script.rbxl (21.7 KB).

Full Model: Global Leaderboard Explained - Roblox
Downloadable Full Model: Global Leaderboard With Script.rbxm (12.5 KB)

Un-copylocked Working DEMO: Leaderboard with script - Roblox
Downloadable Working DEMO: Leaderboard With Script Working DEMO.rbxl (24.7 KB)

Happy Coding

527 Likes
How to script a Global Leaderboard
Global Leaderboard of Race Times
Making global leaderboard with DataStore2
OrderedDataStores uses and documentation?
Leaderboard UI need help with positioning and sizing
Help with DataStore Variables leaderboard
How to make a global leaderboard using DataStore2?
Making a leaderboard
How to make a monthly leaderboard
How to make a Leaderboard like Bubble Gum Simulator
How do I make a separate area for first place on a leaderboard?
Unique Player Count
How do I make a daily leaderboard?
Game-wide/global leaderboards?
How can I make a Global Leaderboard like this?
Scrolling Frame dosen't fit all screens? [Unsolved]
Need a leaderboard displaying how much items a person has
Global leaderboard showing same value
Global leaderboard showing same value
Ordered Datastore doesn't display every player's data
Global Leaderboard Script
Creating a global leaderboard with a normal datastore
Donation board in game
Roblox database question
Adding A Statue For The #1 Player[Global Leaderboard]
How can I get Ordered DataStore for a stored table?
Global leaderboard for goal
Grab all of one variable in a data store?
Grab all of one variable in a data store?
Why doesn't this ordered DataStore work with my leaderboard?
Global leaderboard not working
Need help to make a killing script link with my coin one
Retrieve all data from a DataStore
How to make a Leaderboard like Bubble Gum Simulator
Global Leaderboard - Data Retrieval
Cross Server Leaderboard
Global Leaderboard Gui
Global Leaderboard for time?
Global Leaderboard for time?
[SOLVED] Global Leaderboard
Global GUI stats show
TextLabel to show player stats when not ingame
How could I make a daily leaderboard?
How could I go about making a leaderboard based on leaderstats?
LeaderBoard Not Working
Have problem with weekly leaderboard!
How would I display the highest score in a game?
OrderedDataStore - Problem with tables/Arrays
Can someone help me rq, global leaderboard problems
Best way to move dictionary data one key over
How can I make a global leaderboard with over 100 slots?
Help me in Script
How can I make a daily leaderboard?

Thanks for this thread! I just started coding 2 days ago and tried some small stuff. I think I would try this one out. Amazing tutorial!

27 Likes

Glad you found it useful! If you have any questions, feel free to DM them to me.

20 Likes

Wow this is really nice. Good job! How long did it take you to write this?

9 Likes

I’ve already made countless leaderboards, the problem was trying to make it easy to understand. It took me about 2 hours to get the images and text done. Glad you found it useful.

18 Likes

This is an amazing tutorial on how to make a global leaderboard! I have a question. Why are you using PointsService to handle the ranking? Isn’t it deprecated? (Sorry, about this because you wrote in your post to PM you if there are any questions)

6 Likes

PointsService doesn’t handle the ranking. It’s just the score you can have. You can change it, for example:
local w = player.leaderstats.Points.Value

6 Likes

Yes, it is depreciated. I wanted to use something simple so I did not have to go through how data stores work ect.

Change this line of code:

	for i,plr in pairs(game.Players:GetChildren()) do--Loop through players
		if plr.UserId>0 then--Prevent errors
			local ps = game:GetService("PointsService")--PointsService
>>>>>>>>>>>	local w = ps:GetGamePointBalance(plr.UserId)--Get point balance
			if w then
				pcall(function()
				--Wrap in a pcall so if Roblox is down, it won't error and break.
					dataStore:UpdateAsync(plr.UserId,function(oldVal)
				        --Set new value
						return tonumber(w)
					end)
				end)
			end
		end
	end

If cash was a leaderstat for players, and you wanted to make a leaderboard for it:

local w = plr.leaderstats.Cash.Value

Note: Also remove were pointsService is defined if you do not need it.

If you have more questions, feel free to DM me.

18 Likes

Please help me, I want the list to come out in order but they only come out cluttered, I’ve put “true” where it says that the small numbers appear first but it does not work, they keep coming out just as messy

2 Likes

I have no idea how this went 2 months without anyone saying anything. Change these lines:

local new = sample:Clone()
new.Name = number
--Add this line:
new.LayoutOrder = tonumber(number)

Then, go to ScrollingFrame.UI and change ScrollingFrame.UI.SortOrder to ‘LayoutOrder’.

Roblox’s name ordering system puts 10 after 1. This is why I should of used LayoutOrder.

This should be fixed in the original model now, so you can sort backwards.

7 Likes

Awesome tutorial, I’ve managed to make my global (all-time) leaderboards work perfectly. However I’m also trying to make a daily & weekly leaderboard, but still confuzzled about it. For the all-time leaderboards, I used a value “TotalCash”. This is in the player’s data, and is added to every time the player earns some cash. Though obviously I wouldn’t be able to use it for daily leaderboards, hence why I’m unsure of what to do. I tried looking for some examples of them but couldn’t find any :confused: .Would highly appreciate some ideas on how I could make my daily and weekly leaderboards.

4 Likes

For this simply change the name of the OrderedDataStore based on the week. You can generate a unique weekly-id by dividing the current epoch-time with the number of seconds in a week, then rounding this value:

local dataStoreName = "LeaderboardWeek"
local restartWeekOffset = 86400 * 1 -- Friday (0), Saturday(1), Sunday(2), Monday(3), Tuesday(4), Wednesday(5), Thursday(6)
local secondsInAWeek = 604800
local currentTime = os.time()
local weekId = math.floor((currentTime+restartWeekOffset)/secondsInAWeek)
local finalName = dataStoreName..weekId
local orderedDataStore = dataStoreService:GetOrderedDataStore(finalName)

Same principle applies for a daily-leaderboard, where you’d divide through by the number of seconds in a day, instead of a week.

20 Likes

Just discovered this tutorial now, thanks for creating! Definitely will be trying this out later. :smile:

6 Likes

Ah I get it, it simply changes the OrderedDataStore each week. That’s cool, thanks!

5 Likes

A Mathy Method

@ForeverHD’s Method will work, but you can also utilize os.date to make your life a bit easier instead of doing a ton of math. os.date also factors in leap-days, daylight savings and such.

A Easier Method

Basically, what you do is instead of overwriting data, you just save and get it in a different key. That way, you don’t have to wory about past days or years or anything, you can just change the key. Using os.time and os.date, we make can make a unique key for the day, month, or year.

To acheive daily leaderboards, simply create a unique key for each day using os.date(), storing the year, month, and day. (year because if the game lasts for a year, old data will be used)

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day

In case you didn’t see it, My FAQ section has a bullet on this:

How do I make a daily, monthly, or yearly leaderboard?

To make leaderboards “Reset” we can simply change where we save and get the data from. os.time() gives us the number of seconds since the unix equinox. os.date(time) convertes those seconds into a date.

To acheive daily leaderboards, simply create a unique key for each day using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
--Unique key for each day

To acheive monthly leaderboards, simply create a unique key for each month using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"]
--Unique key for each month

To acheive yearly leaderboards, simply create a unique key for each year using os.date()

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"]
--Unique key for each year
14 Likes

Your welcome! Hope you find it useful. If you have any questions, feel free to ask them.

6 Likes

If I understand this correctly, would the key be the name of the ordered datastore? As it’ll be changing daily/weekly.

Also, I’ve created a value for the player’s Daily and Weekly data. How would I reset these every day and week? image

3 Likes

You could store it like this, and reset when the two value’s don’t equal each other.

Here’s a little pseudo code

local dateTable = os.date("*t", os.time())
local key = "Y:"..dateTable["year"].." M:"..dateTable["month"].." D:"..dateTable["day"]
local getData = --Get player's data
if getData["DayStored"]~=key then
    --New day, reset data and set new key
    getData["DayStored"] = key
    getData["ActualData"] = 0 --Add to this when they gain cash
end
6 Likes

That’s easier than I thought haha, thank you very much!

5 Likes

Glad you found it useful. The same principle would apply for other values, just remember to reset when the day is not the same, and create a separate value to save to other than your long-term storage.

6 Likes