Custom Leaderboard
Overview
Use OrderedDataStore to permanently save numeric scores, such as a player's best record, total eliminations, or clear time, and retrieve them sorted by rank. This allows you to build your own leaderboard, such as an all-time ranking shared across every server or a seasonal ranking.
OrderedDataStore is a key-value store like DataStore, but its values are limited to integers in exchange for sorted retrieval.
Which Feature Should You Use?
Permanently save scores, sort and retrieve them as you like, and build your own leaderboard UI
OrderedDataStore
Save various forms of player data, such as level and inventory
Display rankings in the Top Scorer section of the app and above the character
How to Use
Supported Data Types
OrderedDataStore only stores integers.
Integer (number)
O
Decimal (number)
Not supported.
string
Not supported.
bool
Not supported.
table
Not supported.
Saving an unsupported value raises an error and leaves the existing value unchanged. NaN and Inf cannot be saved either.
For scores with decimal points, such as a clear time of 12.34 seconds, multiply by a fixed scale to store them as integers and divide again when you display them.
Default Structure
Use the GetOrderedDataStore function of DataStoreService to retrieve an OrderedDataStore object with a designated name, and save scores in a key-value format.
Key: A string of 1 to 50 characters that identifies who the score belongs to (e.g. the player's UserId converted to a string)
Value: The integer score used as the sorting criterion (e.g. 1,500)
Unlike DataStore, additional information such as UserIds and Metadata, as well as version history, is not supported. Once a value is overwritten or deleted, the previous value cannot be recovered.
DataStoreService:GetOrderedDataStore(name, scope(*optional))
Retrieve the OrderedDataStore object that corresponds to the name
GetSortedAsync(ascending, pagesize, minValue(*optional), maxValue(*optional))
Retrieve saved scores sorted and divided into pages (returns DataStorePages)
GetAsync(key)
Retrieve the score that corresponds to the key
SetAsync(key, value)
Save (overwrite) a score in the key
IncrementAsync(key, delta(*optional))
Increase/decrease the score that corresponds to the key by delta (1 if delta is omitted)
UpdateAsync(key, callback)
Update the score that corresponds to the key through the callback
RemoveAsync(key)
Delete the score that corresponds to the key
All of these functions can only be used in server scripts. Accessing them from the client raises an error.
Data Store Retrieval
Calling it again with the same name returns the same object, so scripts that retrieve the same name all share one store.
DataStore and OrderedDataStore are separate stores even when the name is identical. The values of
GetDataStore("Score")andGetOrderedDataStore("Score")never mix.Names, scopes, and keys are all case sensitive.
KillLeaderboardandkillLeaderboardare different stores.
Separating Storage with Scope
Provide the second argument, scope, to divide the storage space within one name. If scope is omitted, global is used.
Saving Scores
Keys are strings, so convert a UserId with tostring(). Passing a number also works because it is converted to a string, but retrieved keys always come back as strings, so it is clearer to treat them as strings from the start.
Accumulating Scores
To simply add to or subtract from a score, use IncrementAsync. If the key does not exist, the delta value is saved as the initial value, and the updated score is returned.
Updating Only the Best Record
When you need to decide based on the current value, such as "save only if it is higher than the existing record," use UpdateAsync. Even if multiple servers update the same key at the same time, the decision is made against the latest value, so records are not overwritten.
UpdateAsync on OrderedDataStore differs from DataStore in the following ways.
The callback receives only the current value as its argument. DataStoreKeyInfo is not passed.
The callback returns the new integer value directly, not a table.
It returns the
updated valuewhen the value is saved, andnilwhen the callback returns nil and the write is canceled. A canceled call does not return the current value, so the return value tells you whether the write happened.
Retrieving Scores
GetAsync returns the saved score, or nil if the key does not exist. OrderedDataStore does not support DataStoreKeyInfo, so the second return value is always nil.
Retrieving Rankings
Use GetSortedAsync to retrieve saved scores in sorted order. The result is returned as a DataStorePages object, and GetCurrentPage() gives you the array of entries on the current page.
ascending
Sorts from the lowest score (ascending) when true, and from the highest score (descending) when false. Cannot be omitted.
pagesize
The maximum number of entries per page. Must be between 1 and 100, and cannot be omitted.
minValue
(Optional) Excludes scores lower than this value. The boundary value is included.
maxValue
(Optional) Excludes scores higher than this value. The boundary value is included.
Each entry is a table with a key (string) field and a value (integer) field.
If no scores are saved at all, GetCurrentPage() returns an empty table rather than nil, so you can iterate over it without a separate nil check.
Retrieving a Score Range
Provide minValue and maxValue together to retrieve only the entries within a specific score range. An error occurs if maxValue is smaller than minValue.
Reading Multiple Pages in Order
If IsFinished is false, more pages remain. Use AdvanceToNextPageAsync() to load the next page, and wrap it in pcall because this call can also fail.
Calling AdvanceToNextPageAsync() on the last page raises an error, and the current page and IsFinished remain unchanged.
If you call it again on the same DataStorePages while the next page is still loading, the later call fails immediately. Call it in a single sequential flow as in the example above. Once the preceding call ends, whether by success, failure, or timeout, you can call it again.
Different DataStorePages objects can advance at the same time without affecting each other.
Deleting Scores
Use RemoveAsync to delete a key, which returns the deleted score. The deleted key is excluded from subsequent ranking results.
Deletion is permanent and cannot be undone.
Full Code Example
The following code saves each player's best score and has the server retrieve the top 10 periodically and send the result to every client.
Ranking retrieval has the narrowest request limit, so instead of querying on every client request, the server retrieves once and shares the stored result.
Place a RemoteEvent named S2C_UpdateLeaderboard under ReplicatedStorage before using this code.
Script (ServerScriptService)
LocalScript (StarterPlayerScripts)
SaveBestScore(player, score)
Uses UpdateAsync to save only when the score is higher than the existing record
RefreshTopEntries()
Uses GetSortedAsync to retrieve the top 10, stores them in TopEntries, and sends them to every client
Keeps the previous result and retries on the next cycle if the retrieval fails
Since the saved keys are UserIds, players connected to this server are displayed by name and the rest are displayed by UserId
PlayerAdded
Sends the stored result to a joining player instead of retrieving the rankings again
OnUpdateLeaderboard(entries)
Prints the received rankings as an example, so replace print with code that updates your leaderboard UI
Difference Between Actions in Published and Test Environments
After publishing, scores are saved on the live server in mobile environments, and every server shares the same leaderboard.
However, in studio test environments, a temporary store is used without any additional setup, and the saved data is automatically deleted when the test session ends. Verify actual saving and behavior across multiple servers in the published environment.
Important Notes
Every Call Can Fail
OrderedDataStore functions can fail depending on network conditions or request limits, and failures are delivered as errors. Wrap every call in pcall and decide what should happen on failure.
If you ignore failures, rankings can silently go stale while displaying outdated information.
Do Not Retry a Failed IncrementAsync As Is
A failed save call means the result was not received, not that the value was not saved. Calling IncrementAsync again when the score has already been added can add the score twice.
If you need to retry, saving the final score the server knows about with SetAsync is the safer approach.
Account for Request Limits
The number of requests OrderedDataStore can send within a given period is limited.
Limits apply at two levels, the entire world and each server, and exceeding either one prevents the request from being processed. Limits grow with the number of players, but splitting players into more servers does not raise the world-wide limit.
Ranking retrieval (GetSortedAsync, AdvanceToNextPageAsync) has the narrowest limit. Instead of querying on every client request, share a result the server retrieves periodically, as in the full code example.
Ranking retrieval fails immediately when the limit is exceeded. Try again after a short while.
Saving, deleting, and retrieving a single key do not fail when the limit is exceeded; they wait until they can be processed. The call takes longer than usual, and new requests fail once too many requests are waiting.
GetAsync on OrderedDataStore shares the retrieval limit with GetAsync on DataStore. Frequently loading player data also reduces the headroom available for retrieving scores.
There is no function for checking the remaining number of requests in advance. Write your code assuming requests can be rejected, using pcall and retry intervals.
Retrieved Values Are Not Cached
GetAsync and GetSortedAsync send a new request and use the request limit every time they are called. When the same value is used in several places, retrieve it once, store it in a variable, and share it.
In addition, a value saved on another server may briefly return the previous value.
The Range of Integers Saved Exactly
Only integers within -9,007,199,254,740,991 to 9,007,199,254,740,991 (±(2^53 - 1)) are saved and retrieved exactly.
If your script uses a number outside this range, the value can already be rounded before it is saved, and the original value cannot be recovered.
If a saved value cannot be represented exactly in a script, the retrieval fails rather than returning an approximate value.
Write Only Calculations in the UpdateAsync Callback
When multiple servers update the same key at the same time, the callback can run again with the latest value, and the number of executions is not guaranteed.
Do not perform external actions inside the callback, such as firing a RemoteEvent or writing to another store. They can run more than once.
Do not call waiting functions such as
task.wait()inside the callback. The callback is interrupted at that point and the score is not saved. The call itself returns nil without an error, so pcall cannot detect the failure — you can only confirm it through the error log in the Output window and the nil return value.
The Order of Tied Entries Is Not Guaranteed
Entries with the same score can be ordered differently on each retrieval. If you need a unique rank among tied players, build the criterion into the value itself, such as adding a secondary factor so that the record achieved first ranks higher.
Rankings Can Change While You Page Through Them
The result of GetSortedAsync is not a fixed snapshot taken at retrieval time. If scores are added or deleted after you read the first page, those changes are reflected in the next page, so the same entry can appear twice or be missed. Remove duplicates by key when displaying multiple pages.
Saves Right Before Server Shutdown May Be Partially Applied
Once the server starts shutting down, new requests fail immediately. Among the waiting requests, only the last request per key is processed, and requests not processed within a limited time are discarded.
If several IncrementAsync calls were waiting on the same key, only the last delta is applied and the earlier accumulated amounts are lost.
Instead of saving scores all at once when a player leaves, save them incrementally during gameplay. This reduces how much is lost at shutdown.
Usage Examples
Accumulate records such as eliminations or collected coins with IncrementAsync to build an all-time cumulative ranking.
For games where a lower record is better, such as speedruns, convert the clear time into an integer and retrieve it in ascending order with
GetSortedAsync(true, ...).Specify a season name as the scope to run a new leaderboard each season while keeping previous seasons available for retrieval.
Retrieve score ranges with minValue and maxValue to build tier-based rankings such as bronze, silver, and gold.
Use WorldRankService for the app and above-character ranking display, and OrderedDataStore for the detailed leaderboard inside the world.
Last updated