Every good game needs to remember what players did. DataStore is Roblox's way of saving that information to the servers so it stays safe. Whether you're tracking level, currency, inventory, or stats — DataStore keeps it persistent.
Let's build a working system from scratch. You'll see exactly where each script goes and why it works.
What Is DataStore?
DataStore is a built-in Roblox service that stores player data on Roblox's servers. When a player leaves your game and comes back a week later, their data is still there. Unlike variables in your script (which disappear when the server restarts), DataStore data is permanent.
There are two main types:
- Regular DataStore — stores one value per key (best for simple data like level or coins).
- OrderedDataStore — keeps data sorted by value (best for leaderboards).
For now, we'll focus on regular DataStore since it's what most games need.
Setting Up DataStores
First, enable DataStore in your game settings. Go to Home → Game Settings → Security, and make sure Allow HTTP Requests is checked. This lets your scripts communicate with Roblox's servers.
Next, get access to the DataStore service at the top of your script:
local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("PlayerData")The string "PlayerData" is the name of your DataStore. You can have multiple DataStores for different purposes — one for levels, one for inventory, etc. Roblox will create it automatically the first time you access it.
Saving Player Data
The safest place to save data is when a player leaves the game. Use the PlayerRemoving event in a Script inside ServerScriptService.
local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("PlayerData")
local Players = game:GetService("Players")
Players.PlayerRemoving:Connect(function(player)
local key = "Player_" .. player.UserId
local data = {
level = player:FindFirstChild("Level").Value,
coins = player:FindFirstChild("Coins").Value
}
local success, errorMessage = pcall(function()
playerDataStore:SetAsync(key, data)
end)
if success then
print("Saved data for " .. player.Name)
else
print("Failed to save data: " .. errorMessage)
end
end)Let's break this down:
key = "Player_" .. player.UserId— Each player gets a unique key based on their user ID. This makes sure one player's data doesn't overwrite another's.data = {...}— A table holding all the player's stats. You can add as many fields as you need.pcall(...)— This wraps the save in a protected call. If something goes wrong (network error, timeout), the game doesn't crash. Instead,successis false anderrorMessagetells you what happened.SetAsync(key, data)— Saves the data.Asyncmeans it happens in the background so your game doesn't freeze.
pcall with DataStore calls. Networks fail sometimes, and your game should handle it gracefully.Loading Player Data on Join
When a player joins, load their saved data. Use the PlayerAdded event, also in a Script in ServerScriptService:
local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("PlayerData")
local Players = game:GetService("Players")
Players.PlayerAdded:Connect(function(player)
local key = "Player_" .. player.UserId
-- Create value objects to hold the data
local levelValue = Instance.new("IntValue")
levelValue.Name = "Level"
levelValue.Parent = player
local coinsValue = Instance.new("IntValue")
coinsValue.Name = "Coins"
coinsValue.Parent = player
-- Load the data
local success, data = pcall(function()
return playerDataStore:GetAsync(key)
end)
if success and data then
levelValue.Value = data.level
coinsValue.Value = data.coins
print("Loaded data for " .. player.Name)
else
-- First-time player (no saved data)
levelValue.Value = 1
coinsValue.Value = 0
print("New player: " .. player.Name)
end
end)Here's what happens:
- We create IntValue objects to hold the player's level and coins. These live inside the player object so other scripts can easily access them.
GetAsync(key)retrieves the saved data. If the player joined for the first time,dataisnil.- If data exists, we restore their level and coins. If not, they start fresh with level 1 and 0 coins.
Complete DataStore System
Putting it all together, here's a production-ready script you can paste into ServerScriptService:
local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("PlayerData")
local Players = game:GetService("Players")
-- Load data when player joins
Players.PlayerAdded:Connect(function(player)
local key = "Player_" .. player.UserId
-- Create value objects
local levelValue = Instance.new("IntValue")
levelValue.Name = "Level"
levelValue.Parent = player
local coinsValue = Instance.new("IntValue")
coinsValue.Name = "Coins"
coinsValue.Parent = player
-- Load saved data
local success, data = pcall(function()
return playerDataStore:GetAsync(key)
end)
if success and data then
levelValue.Value = data.level
coinsValue.Value = data.coins
print("Loaded data for " .. player.Name)
else
levelValue.Value = 1
coinsValue.Value = 0
print("New player: " .. player.Name)
end
end)
-- Save data when player leaves
Players.PlayerRemoving:Connect(function(player)
local key = "Player_" .. player.UserId
local data = {
level = player:FindFirstChild("Level").Value,
coins = player:FindFirstChild("Coins").Value
}
local success, errorMessage = pcall(function()
playerDataStore:SetAsync(key, data)
end)
if success then
print("Saved data for " .. player.Name)
else
print("Failed to save data: " .. errorMessage)
end
end)This single script handles both loading and saving. Place it in ServerScriptService and it runs automatically.
Updating Data During Play
You don't save every single change to DataStore — that's too slow. Instead, update the IntValue in the player, and save everything when they leave. Here's how other scripts change the data:
local player = game.Players:WaitForChild("PlayerName") -- or however you find the player
local coinsValue = player:WaitForChild("Coins")
coinsValue.Value = coinsValue.Value + 50
print("Player now has " .. coinsValue.Value .. " coins")The value updates instantly for the player to see. When they leave, the DataStore save script automatically persists it.
Error Handling Best Practices
Network issues happen. Here's how to handle them without breaking your game:
local function savePlayerDataWithRetry(player, maxRetries)
maxRetries = maxRetries or 3
local key = "Player_" .. player.UserId
local data = {
level = player:FindFirstChild("Level").Value,
coins = player:FindFirstChild("Coins").Value
}
for attempt = 1, maxRetries do
local success, errorMessage = pcall(function()
playerDataStore:SetAsync(key, data)
end)
if success then
print("Saved data for " .. player.Name)
return true
else
print("Save attempt " .. attempt .. " failed: " .. errorMessage)
if attempt < maxRetries then
wait(2) -- wait 2 seconds before retrying
end
end
end
print("Failed to save data for " .. player.Name .. " after " .. maxRetries .. " attempts")
return false
endThis tries up to 3 times before giving up, with a 2-second delay between tries. It gives the network a chance to recover.
Testing DataStores Locally
When you test in Studio on your own computer, DataStore works but data resets when you stop the game. This is intentional — it prevents test data from clogging your real database.
To test properly:
- Run your game in Studio.
- Join as a player and earn some coins or progress.
- Leave the game.
- Check the output console — you should see "Saved data for [YourName]".
- Join again — you should see "Loaded data for [YourName]".
If you see errors, read them carefully. They usually tell you exactly what went wrong (permissions, network, bad key format, etc.).
DataStore Tips & Tricks
Use descriptive key names. Instead of just player.UserId, use "Player_" .. player.UserId .. "_v1". If you ever need to change your data structure, the version number helps you keep old and new data separate.
Save on a timer, not every change. If you're tracking position or health, don't save 60 times per second. Instead, auto-save every 30 seconds or when something major happens (level up, quest complete).
Compress large data. If you're storing a huge table with hundreds of items, consider using game:GetService("HttpService"):JSONEncode() and JSONDecode() to serialize it safely.
Test your load logic. What happens if the save file is corrupted or doesn't exist? Your code should gracefully handle it, not crash.