How To Use, Utilize, and Succeed with Datastores
Saving data is an important part of creating your experience. It allows your players to return and continue where they left off, without needing to start over again.
Many of you will most likely use Datastores at one point or another in your journey on Roblox. Many tutorials on YouTube and the Developer Forum cover this topic, but many of them are outdated and use methods that shouldn’t be used today.
I’ll be guiding you step-by-step through this tutorial to help you succeed. If you have any questions about this, let me know in the comments below!
1. Getting Started
First, you need to know what a data store is. A data store in Roblox is a holder that contains data. It can only save digits and is connected to your account through your user ID.
To synchronize and save the data, you can use DataStore:SetAsync()
or DataStore:UpdateAsync()
. To retrieve data, you’ll use DataStore:GetAsync()
.
Hold Up! What is the difference between SetAsync() and UpdateAsync()?
SeyAsync() gets the latest data changes and overwrites this into our connected data store, while UpdateAsync() retrieves the value and data from our key and then proceeds to change the data store. It’s recommended to use UpdateAsync() rather than SetAsync() because SetAsync() often leads to data losses if the game is immediately shut down or the player randomly disconnects.
You can only access DataStores on the server, such as with Scripts and ModuleScripts. You can not access the service on the client.
There are also limits on how many requests you can send to a data store, however, these are not very common to come across unless you spam :SetAsync()
in an infinite loop. You can view these limits here.
2. Setting up in Studio
In ServerScriptService, create a new Script and name it “DataStoreHandling”. We’ll use this script for executing our code.
Next, add a new ModuleScript to the script we just created, and name it “dataModule”. It should look like this now:
Now, open the script dataModule.
3. Starting with the Modules
Inside the dataModule script, it’ll look like this:
local module = {}
return module
Rename the variable “module” to “dataModule”, and change it to both of the lines we have. It should look something like this now:
local dataModule = {}
return dataModule
Next, we’re going to be creating 3 functions. These functions will be essential in our saving and loading procedures. We’ll also use type-checking to ensure that we get the right player model for our script.
Create three functions named addInstances
, saveData
and loadData
in the dataModule script. It should look something like this:
function dataModule.addInstances()
-- // This will add our instances to the player
end
function dataModule.saveData()
-- // This will save our data upon client disconnect.
end
function dataModule.loadData()
-- // This will load our data once the client connects to the server
end
Now we’ll use something called type-checking. Type-checking is when we make the script double-check that the value of our requested package is the same.
Type-checking Example
For example, if I wanted to make a for loop
with a StringValue
that prints out the value of the strings, I can do something like this:
for i, obj: StringValue in ipairs(workspace:GetChildren()) do
print(obj.Value)
end
We’ll add a player: Player
instance to each of the functions, like this:
function dataModule.addInstances(player: Player)
end
function dataModule.saveData(player: Player)
end
function dataModule.loadData(player: Player)
end
4. Going deeper into the Code
Now we’ll go deeper within each function, and create the fundamentals for it. I recommend going through the following paragraphs exactly as outlined below, otherwise you may encounter some problems with your code.
4a. addInstances()
4a. Adding Instances
Firstly, we’ll need some values that we can save our player’s data on. For this tutorial we’re going to be using an IntValue named “Points”, but you can rename it to whatever you’d like.
We’ll start by creating a new folder. This folder is going to be named “Leaderstats”, and we’ll set the value to “leaderstats”. Then, we’ll set the parent of our folder to the player we defined earlier.
WAIT! Why can’t we name the value of the folder as “Leaderstats”?
Due to limitations within the Roblox engine, we’re only able to call the Leaderstats function in lowercase. You can bypass this if you don’t want to show it in the leaderboard but in a custom GUI instead.
Your code should then look like this:
function dataModule.saveData(player: Player)
local Leaderstats = Instance.new("Folder")
Leaderstats.Name = "leaderstats"
Leaderstats.Parent = player
end
Now we’re going to be creating a new instance, an “IntValue”. This will be where our points will be stored and visible to the player through the leaderboard. We’ll name the value as “Points”, and set the value to 0.
function dataModule.addInstances(player: Player)
local Leaderstats = Instance.new("Folder")
Leaderstats.Name = "leaderstats"
Leaderstats.Parent = player
local pointsValue = Instance.new("IntValue")
pointsValue.Name = "Points"
pointsValue.Value = 0
pointsValue.Parent = Leaderstats
end
4b. saveData()
4b. Saving the Data
The next thing we’re going to do is make a function that saves our data. We’ll start by making 2 variables, one where we call DataStoreService
and one where we create our data store.
You can name your data store variable and value as whatever you’d like, as long as you remember the name of it. For this tutorial, we’re going to name it dataStore
and give it the value DataStore
.
Warning
Make sure to save the name of the value inside the parentheses of the datastore value. This is to ensure that we call the same store every time we call it from a different script or function. Otherwise, we might encounter some problems. You can also call the leaderstats folder here, as we’ll need it further.
Your script should now look like this:
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
end
Next, we’re going to create 3 new variables. One of these will be the key. This is where we define where our data is stored, and in this case, it’s our player’s user ID. Then, we’re going to define the data. This is the value that we want to save in our data store. If you want to save multiple values, see section 7 for a deeper input on that.
Finally, we’re going to create a success, result variable for our pcall. If you don’t understand what a pcall is, or you want to learn more on how to use them, check out this tutorial for a deeper explanation.
After you’ve done this, your code should look like this:
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
-- // New Variables
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
end
Now we’re going to create a repeat until
loop. This means that we want something to repeat a set amount of times until we receive what we want. In this case, we’ll be repeating our data saving until it has been successfully saved. To do this, start by typing repeat
in a line of the script, and click enter. The until
option will automatically be added once you do this.
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
-- // Our repeat until loop
repeat
until
end
After this, we can use the variable we made earlier, success, result
, to call out our pcall. Then, we’ll save the data by using the UpdateAsync()
function, and then set it to return the data.
After that, we’ll need to add a task.wait()
after our pcall, to ensure that the engine waits a few seconds before attempting to save again. Finally, we’ll set the until
value to success
.
If we don’t add a
task.wait()
our program will crash due to the infinite request the server sends to the datastore servers.
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
repeat
success, result = pcall(function() -- // Adding our pcall function
dataStore:UpdateAsync(Key, function() -- Saving the data using UpdateAsync()
return data
end)
end)
task.wait()
until success -- // If the save is a success, cancel the loop. If not, the loop continues.
end
It’s always a good idea to print out potential errors that occur in our code. In addition, we can print out if the save was a success. To do this, we’ll use a if not, elseif, else
check, and then use the print()
element to print out the error messages and success.
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
repeat
success, result = pcall(function()
dataStore:UpdateAsync(Key, function()
return data
end)
end)
task.wait()
until success
-- // Adding our if-statements to check for errors or success
if not success then
elseif success then
else
end
end
Then, we can make it so if it is not success, then we use the warn()
function to print the error message. If it is success, then we can use print()
to print out our success message, and finally, if a different error occurs, we can warn out that error as well.
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
repeat
success, result = pcall(function()
dataStore:UpdateAsync(Key, function()
return data
end)
end)
task.wait()
until success
if not success then
warn("An error occurred while attempting to override DataStore values, see here for more: " .. tostring(result))
elseif success then
print("Saved data to user " .. player.Name)
else
warn("An error occurred, see for more: " .. tostring(result))
end
end
To ensure that the
result
message can be displayed in a string, we can use thetostring()
element to turn our error message into a string that we can connect to our main printing.
4c. loadData()
4c. Loading the data
It’s probably a good thing to load our player data once the player connects to our server. Loading the data is almost the same procedure as saving the data, but there are a few differences.
First, we can start by creating the same variables we created in our saveData() function. In addition, we’ll add two new elements.
The leaderstats
folder from the player
, and the IntValue
from our folder. This time we’ll also be setting our data
variable to nil, as we’ll be defining the data later on.
function dataModule.loadData(player: Player)
local PlayerService = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local Key = player.UserId
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local pointsValue = leaderstatsFolder:FindFirstChild("Points")
local data
local success, result
end
Next up we’ll be adding a new repeat until
loop. This time we’ll be using GetAsync()
instead of UpdateAsync()
, as we’re getting our data, and not saving it. We’ll only be using our key
to retrieve the data, and we do not connect it to a function.
Like with the saving data, we’ll be setting our until
value to success.
However, since we’re loading the data, and not just saving it, it can cause a problem if the player suddenly disconnects and the loop throws an error.
To fix this, we’ll simply add a check to ensure that the player is still present. If not, it’ll cancel the loop and the loading process.
function dataModule.loadData(player: Player)
local PlayerService = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local Key = player.UserId
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local pointsValue = leaderstatsFolder:FindFirstChild("Points")
local data
local success, result
-- // Our repeat until loop
repeat
success, result = pcall(function()
data = dataStore:GetAsync(Key) -- // Getting the data from our key
end)
task.wait()
until success or not PlayerService:FindFirstChild(player.Name) -- // Continue until loading is successful, or until the player doesn't exist anymore in the player list.
end
Now we’ll be adding an if, elseif not, else
check to ensure that if it’s a success, then we print out a success message and set the value to the data. If it’s not a success, we’ll print out the error in a string format, and if any other errors occur, we’ll print that error.
function dataModule.loadData(player: Player)
local PlayerService = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local Key = player.UserId
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local pointsValue = leaderstatsFolder:FindFirstChild("Points")
local data
local success, result
repeat
success, result = pcall(function()
data = dataStore:GetAsync(Key)
end)
task.wait()
until success or not PlayerService:FindFirstChild(player.Name)
if success then
pointsValue.Value = data
print("Successfully loaded the data for user " .. player.Name)
elseif not success then
warn("An error occurred while trying to load the data, see here for more: " .. tostring(result))
else
warn("An error occurred, see here for more: " .. tostring(result))
end
end
Final Result
Our final code in the dataModule
should look like this:
local dataModule = {}
function dataModule.addInstances(player: Player)
local Leaderstats = Instance.new("Folder")
Leaderstats.Name = "leaderstats"
Leaderstats.Parent = player
local pointsValue = Instance.new("IntValue")
pointsValue.Name = "Points"
pointsValue.Value = 0
pointsValue.Parent = Leaderstats
end
function dataModule.saveData(player: Player)
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local Key = player.UserId
local data = leaderstatsFolder.Points.Value
local success, result
repeat
success, result = pcall(function()
dataStore:UpdateAsync(Key, function()
return data
end)
end)
task.wait()
until success
if not success then
warn("An error occurred while attempting to override DataStore values, see here for more: " .. tostring(result))
elseif success then
print("Saved data to user " .. player.Name)
else
warn("An error occurred, see for more: " .. tostring(result))
end
end
function dataModule.loadData(player: Player)
local PlayerService = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("DataStore")
local Key = player.UserId
local leaderstatsFolder = player:FindFirstChild("leaderstats")
local pointsValue = leaderstatsFolder:FindFirstChild("Points")
local data
local success, result
repeat
success, result = pcall(function()
data = dataStore:GetAsync(Key)
end)
task.wait()
until success or not PlayerService:FindFirstChild(player.Name)
if success then
pointsValue.Value = data
print("Successfully loaded the data for user " .. player.Name)
elseif not success then
warn("An error occurred while trying to load the data, see here for more: " .. tostring(result))
else
warn("An error occurred, see here for more: " .. tostring(result))
end
end
return dataModule
5. DataStoreHandling Server Script
Now we’ll start connecting our module to our events so that the data can be correctly saved and loaded. We’ll start by creating a variable and using the require()
function to retrieve our code.
local dataModule = require(script.dataModule)
Next, we’ll be retrieving our functions from the module. To do this, we can use the module.function
method to retrieve it, like this:
local dataModule = require(script.dataModule)
-- // Getting our functions from the module
local addInstances = dataModule.addInstances
local saveData = dataModule.saveData
local loadData = dataModule.loadData
Now we’re going to create 2 events, one PlayerAdded
and one PlayerRemoving
event. In these, we’ll be connecting our functions to make sure they run effectively and correctly. We’re also going to add the PlayerService
service as a variable.
We’ll also be installing the player
object as a parameter in each function, to ensure that the code knows which player we’re referring to.
local dataModule = require(script.dataModule)
local addInstances = dataModule.addInstances
local saveData = dataModule.saveData
local loadData = dataModule.loadData
local PlayerService = game:GetService("Players")
-- // Player Added Event
game.Players.PlayerAdded:Connect(function(player: Player)
addInstances(player)
loadData(player)
end)
-- // PlayerRemoving Event
game.Players.PlayerRemoving:Connect(function(player: Player)
saveData(player)
end)
Finally, we’re adding a game:BindToClose()
function. In the case of an immediate game shutdown or client disconnection, we want the server to safely save our data before the disconnection happens.
In this function, we’ll be adding a for
loop, which will run through every player instance in the server, and then save the data to the corresponding player.
game:BindToClose(function()
for i, player: Player in ipairs(PlayerService:GetPlayers()) do
saveData(player)
end
end)
Our final result for the DataStoreHandling script will be this:
local dataModule = require(script.dataModule)
local addInstances = dataModule.addInstances
local saveData = dataModule.saveData
local loadData = dataModule.loadData
local PlayerService = game:GetService("Players")
game.Players.PlayerAdded:Connect(function(player: Player)
addInstances(player)
loadData(player)
end)
game.Players.PlayerRemoving:Connect(function(player: Player)
saveData(player)
end)
game:BindToClose(function()
for i, player: Player in ipairs(PlayerService:GetPlayers()) do
saveData(player)
end
end)
6. Testing out our script!
7. Adding more values
In this case, if you want to save more than 1 value, we’ll need to make some changes in our script.
Start by creating a new IntValue
with its properties in the addInstance()
function, like this:
-- // New IntValue + Properties
local moneyValue = Instance.new("IntValue")
moneyValue.Name = "Money"
moneyValue.Value = 0
moneyValue.Parent = Leaderstats
Next, we need to transform our data
variable in the saveData()
function from a singular
value to a table
value. We do this by doing data = {}
, and then inserting our values through there.
To make a value, call the leaderstatsFolder
+ Name
+ Value
, like this:
local data = {
leaderstatsFolder.Points.Value,
leaderstatsFolder.Money.Value
}
Make sure to do this step for EVERY value you create in your experience. Otherwise, it won’t be saved properly. You don’t need to change anything in the
UpdateAsync
function.
Finally, set the data of each value to the corresponding line it represents. Unlike arrays, tables start with 1
and go up. The formula for doing this is obj.Value = data[n]
.
Since we have two values, we’ll use the numbers 1 and 2, with Points
representing 1 and Money
representing 2.
if success then
-- // Getting our data from our table
pointsValue.Value = data[1]
moneyValue.Value = data[2]
print("Successfully loaded the data for user " .. player.Name)
elseif not success then
warn("An error occurred while trying to load the data, see here for more: " .. tostring(result))
else
warn("An error occurred, see here for more: " .. tostring(result))
end
You can do this for as many values you’d like, as long as you repeat the steps above.
8. The End!
Thank you for visiting and reading this tutorial! I hope that it came across as resourceful and beneficial to your career on Roblox.
If you’re too lazy to write or go through all of this, I’ve created a module that contains the singular saving method which you can get here!
If you have any questions or feedback related to this tutorial, feel free to send those in the comments below!