How to save data with Roblox Data Stores
Saving data with Roblox data stores is essential to keep your player’s progress and inventory. Let’s learn how to use Roblox data stores and useful design patterns to keep your code clean.
What you’ll learn
- Different ways to use a Roblox data store
- How to manage player game data
- Properly handle errors and data store limits
Steps to enable data stores
You must enable data stores to test them within Roblox Studio.
- Publish your game to Roblox
- Open game settings from the home tab in Roblox Studio
- Under the security section, toggle on “Enable Studio Access to API Services”
Be careful with enabling this feature since it uses the same data as your published game.
Uses for Roblox data store
Data stores use a key-value system. For example, we can use a general “GameItems” key to store a list of game items. With a unique key such as “Player_{playerId}”, we can store data that’s only relevant to one player.
By default, data stores keep information in an unordered state. To get data in an order state we’ll use OrderedDataStore. This is especially useful for getting player ranks or lists of items in a player’s inventory.
Ordered Data example
local DataStoreService = game:GetService(“DataStoreService”)
local playerScoresDataStore = DataStoreService:GetOrderedDataStore(“PlayerScores”)
local pages = playerScoresDataStore:GetSortedAsync(false, 20)
local playerScores = pages:GetCurrentPage()
for index, data in ipairs(playerScores) do
local playerName = data.name
local playerScore = data.score
print(playerName .. “ score: “ .. playerScore) -- print out or display player scores in game
end
If you’re looking to make a long list of data visible for all players, we can use DataStorePages. With DataStorePages, we can render a small subset of data with buttons in the UI for player’s to display the next page of data.
We can categorize and organize data stores using metadata such as tags. With the SetMetaData function, it’s recommended to use a descriptive name such as “WeaponType” and its various possible values such as “MagicSpell”, “Gun”, or “Melee”.
Metadata is also useful for storing player’s purchase history by using the userIds parameter in the SetAsync function. Also, when needed for user privacy reasons, we can query and remove all data relating to a user id.
Metadata example
local DataStoreService = game:GetService(“DataStoreService”)
local weaponsDataStore = DataStoreService:GetDataStore(“WeaponsCatalog”)
local setOptions = Instance.new(“DataStoreSetOptions”)
setOptions:SetMetaData({[“WeaponType”] = “Gun”})
local success, error = pcall(function()
local userIds = {} -- array of relevant user ids
weaponsDataStore:SetAsync(“1911_pistol”, userIds, setOptions)
end)
We can also design our in-game Gui interfaces with remote toggle switches. For example, certain features may only exist for players with special privileges such as game administrators. We can use a role based system to give certain players access to special commands and tools.
Handling Roblox data store errors and limits
Data stores may occasionally fail when operating on them. It’s recommended to use Lua’s pcall function to wrap these calls. With the pcall function, we can handle error states in script.
Behind the scenes, Roblox makes remote requests for data store operations. To prevent abuse and keep it fair for all games, Roblox rate limits these operations. If a game exceeds these limits, requests get placed into a queue. Requests placed into a queue will delay the data store’s response.
Code Example
local DataStoreService = game:GetService(“DataStoreService”)
local playerScoresDataStore = DataStoreService:GetDataStore(“PlayerScores”)
local success, playerScores = pcall(function()
playersDataStore:SetAsync(“Player_{somePlayerId}”, 100)
end)
if not success then
-- handle error or retry
end
Limits by Operation
Operation | Rate Limit (requests per minute) |
Get | 60 + numberOfPlayers * 10 |
Set | 60 + numberOfPlayers * 10 |
Get Sorted | 5 + numberOfPlayers * 2 |
Get Version | 5 + numberOfPlayers * 2 |
List | 5 + numberOfPlayers * 2 |
Remove | 5 + numberOfPlayers * 2 |
Roblox also caches these data operations separately by each instance. This means that retrieving data with one scope type is not immediately consistent when retrieved through a different scope.
Typically the Roblox data stores reach consistency within 4 seconds.
Also note that write operations such as SetAsync, UpdateAsync, RemoveAsync have a 6 second delay when used on the same key.
How to use Roblox data store
To set a key-value pair in a store use the SetAsync function.
local DataStoreService = game:GetService(“DataStoreService”)
local playerScoresDataStore = DataStoreService:GetDataStore(“PlayerScores”)
local success, playerScores = pcall(function()
playersDataStore:SetAsync(“Player_{somePlayerId}”, 100)
end)
SetAsync is best for quick updates but still must wait for replication for full consistency across all servers.
Reading a key-value pair uses the GetAsync function.
local DataStoreService = game:GetService(“DataStoreService”)
local playerScoresDataStore = DataStoreService:GetDataStore(“PlayerScores”)
local success, playerScores = pcall(function()
playersDataStore:GetAsync(“Player_{somePlayerId}”)
end)
Updating key-value pairs uses the UpdateAsync function.
local DataStoreService = game:GetService(“DataStoreService”)
local playerScoresDataStore = DataStoreService:GetDataStore(“PlayerScores”)
local success, playerScores = pcall(function()
playersDataStore:UpdateAsync(“Player_{somePlayerId}”, 500)
end)
UpdateSync counts as a read and a write for request limits. It’s also a slower method of storing data but remains consistent across all servers.
Removing a key from a data store uses the RemoveAsync function.
local DataStoreService = game:GetService("DataStoreService")
local playerScoresDataStore = DataStoreService:GetDataStore("PlayerScores")
local success, playerScores = pcall(function()
playersDataStore:RemoveAsync("Player_{somePlayerId}")
end)
If you need to maintain a history, data stores allow you to version each key-value pair for 30 days.
Using the ListVersionsAsync function gets you all versions of a specific key. The GetVersionAsync and RemoveVersionAsync functions allow you to manage specific versions for a specific key.
Recommended Roblox Data Store Guidelines
Before scripting the data stores you’ll use, first consider how they’ll get used in game. Consider how often data will change and what data sets need versioning.
Any frequently accessed data should use the MemoryStoreService. If you’re familiar with backend systems, this is similar to using memcached or redis. This service acts as a quicker cache of data versus always querying a slow or rate limited database.
We can use a server script to preload this type of data as well as update it at a regular interval. This helps with data performance and keeps requests within the rate limits.
Come up with a key schema that makes the most sense based on the query patterns you’ll need. Keep similar and related data in the same data store.
Example
Let’s say we want to store a bunch of stats such as:
- How many steps a player travelled
- Player death count
- Item usage count
- Level played count
We want to easily query the data store to get this information for one player and get totals for all players.
For a single player, we’ll only read the data when they view their stats. We’ll use a key using the following template:
player_{userId}
We’ll store an object that contains all player information that includes their stats, purchases, and any in-game options.
{
player_{userId} = {
purchases = {}, -- list of purchases made by player
stats = {
distance_travelled = 0,
death_count = 0,
items_used = {
powerUp1 = 0,
powerUp2 = 0
}
}
}
}
For all players, we’ll update and read the data when a round ends or at a set interval such as every 5 minutes or longer. We’ll use a key such as all_players with an object key named stats.
We need to think of this as aggregate data that is also obtainable by analyzing all player records individually. In the future, Roblox may open this up further with their Open Cloud API initiative.
{
all_players = {
purchases = {},
stats = {
distance_travelled = 0,
death_count = 0,
items_used = {
powerUp1 = 0,
powerUp2 = 0
}
}
}
}
For this type of system, we can hide or abstract the data layer logic behind a PlayerStatsService script. This script uses Remote events to update the database. That way, your game play scripts only have to fire off these events without getting mixed up with data store logic.
What’s Next
You now have a better understanding of how to use Roblox data stores for your games. I’d recommend reviewing how Roblox games work to understand how data stores connect with everything else.
Have your own API or database? Check out the Roblox HttpService Guide.
Please consider signing up for my email newsletter to get future updates.
Thank you for reading Tandem Coder guides, as always – stay curious!