To save player data in Roblox, use DataStoreService in a server script. Load each player's data with GetAsync when they join, keep it in a table while they play, and save it with UpdateAsync when they leave and when the server shuts down. Wrap every DataStore call in pcall, because they can fail, and turn on Studio access to API services so you can test it.
Below is a complete script you can paste into your game, followed by an explanation of each part and the mistakes that cause lost data. Lost progress is the fastest way to lose players, so this is worth getting right.
Before You Start: Turn On API Access
DataStores only work in published games. To test them in Studio:
- Publish your game (File > Publish to Roblox).
- Open Game Settings > Security.
- Turn on Enable Studio Access to API Services and save.
Without this, your script will throw errors in Studio even if the code is perfect.
The Complete Script
Create a Script (not a LocalScript) in ServerScriptService and paste this:
local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local store = DataStoreService:GetDataStore("PlayerData_v1")
local DEFAULTS = { Coins = 0, Level = 1 }
local loaded = {} -- player -> their data while they're in the game
local function keyFor(player)
return "player_" .. player.UserId
end
local function load(player)
local data
local ok, err
for attempt = 1, 3 do
ok, err = pcall(function()
data = store:GetAsync(keyFor(player))
end)
if ok then break end
task.wait(2)
end
if not ok then
-- Never let them play on empty data: saving it later would wipe their real progress
warn("Couldn't load data for " .. player.Name .. ": " .. tostring(err))
player:Kick("We couldn't load your progress. Please rejoin in a minute.")
return
end
if not player:IsDescendantOf(Players) then return end -- they left while loading
data = data or {}
for name, value in DEFAULTS do
if data[name] == nil then
data[name] = value
end
end
loaded[player] = data
-- Leaderstats show up on the player list in the top right
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = player
for name in DEFAULTS do
local stat = Instance.new("IntValue")
stat.Name = name
stat.Value = data[name]
stat.Parent = leaderstats
stat.Changed:Connect(function(value)
data[name] = value
end)
end
end
local function save(player)
local data = loaded[player]
if not data then return end
local ok, err = pcall(function()
store:UpdateAsync(keyFor(player), function()
return data
end)
end)
if not ok then
warn("Couldn't save data for " .. player.Name .. ": " .. tostring(err))
end
end
Players.PlayerAdded:Connect(load)
for _, player in Players:GetPlayers() do
task.spawn(load, player)
end
Players.PlayerRemoving:Connect(function(player)
save(player)
loaded[player] = nil
end)
-- Save everyone when the server shuts down
game:BindToClose(function()
local pending = 0
for _, player in Players:GetPlayers() do
pending += 1
task.spawn(function()
save(player)
pending -= 1
end)
end
while pending > 0 do
task.wait()
end
end)
-- Autosave every 5 minutes in case a server crashes
task.spawn(function()
while true do
task.wait(300)
for _, player in Players:GetPlayers() do
task.spawn(save, player)
end
end
end)
Press Play, change your coins in the Explorer while testing (Players > your name > leaderstats > Coins), stop, then play again. Your coins should still be there.
To give a player coins from another server script, just change the value: player.leaderstats.Coins.Value += 10. The script above notices the change and saves it.
How It Works
Leaderstats
A folder named exactly leaderstats inside a player makes Roblox show its values on the player list. That's all leaderstats is: a display. The saving is done separately by the DataStore.
Loading
GetAsync fetches the saved table for that player's key. New players have no saved data yet, so the script fills in defaults. Using the player's UserId in the key, not their name, matters because players can change their names.
Saving
UpdateAsync writes the player's table back. It's the safer choice over SetAsync for data that changes, and it's the one Roblox recommends for player data.
The script saves in three situations: when a player leaves, every five minutes, and when the server shuts down (BindToClose). Skipping that last one is a classic cause of lost progress, because when Roblox closes a server, players don't always get a normal "leaving" event first.
Mistakes That Lose Player Data
- Saving after a failed load. If loading fails and the player keeps playing with default data, the next save overwrites their real progress with zeros. That's why the script kicks the player instead.
- Saving from a LocalScript. DataStores only work on the server. Clients can't and shouldn't be able to touch them.
- Saving too often. DataStores have limits on how many requests a game can make. Save on leave, on shutdown and on a timer, not every time a coin changes.
- Renaming your DataStore by accident.
PlayerData_v1is a name. Change it and every player starts from scratch. Only change it on purpose. - Storing things that aren't plain data. Save numbers, strings, booleans and tables of those. You can't save Parts, Instances or Color3 values directly.
When Your Game Grows
The script above is a solid start for small and medium games. Bigger games usually add session locking, which stops a player's data from being loaded on two servers at once (for example, if they hop servers quickly). Rather than building it yourself, many developers use a community module like ProfileStore, which handles locking, retries and autosaves.
Each key can hold up to 4 MB, which is far more than coins and levels need, but worth knowing if you plan to save whole inventories or builds.
Saving data is one of the core skills in our plan for learning Roblox scripting, and it's what makes things like game passes and currency feel worth paying for.
Common Questions
Why is my DataStore not saving in Studio?
Most often, Studio access to API services is turned off (Game Settings > Security) or the game was never published. Also check you're using a server Script, not a LocalScript.
Should I use SetAsync or UpdateAsync?
Use UpdateAsync for player data. SetAsync simply overwrites what's there, which is fine for one-off values but riskier for data that changes from more than one place.
How do I make a leaderboard on Roblox?
Create a folder named leaderstats inside each player with an IntValue for each stat, as in the script above. For a global top-100 board, look up OrderedDataStore.
Does Roblox delete saved data?
No. Data stays saved as long as the DataStore exists, unless you remove it, or Roblox removes a user's data at their request under privacy laws.



