Tutorials

How to Save Player Data in Roblox: DataStores and Leaderstats Explained

Share on X
How to Save Player Data in Roblox: DataStores and Leaderstats Explained

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:

  1. Publish your game (File > Publish to Roblox).
  2. Open Game Settings > Security.
  3. 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_v1 is 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.

ScriptingDataStores

See what Roblox games earn.

Ideas, analytics and Live Ops for Roblox developers. Start with the free tools.