Roblox data store script: save player data

Save coins, levels and progress between sessions — a complete DataStoreService script that won't wipe a player's data when Roblox has a hiccup.

ScriptUpdated November 2, 2026

Where to put it

Script — ServerScriptService > PlayerData (Script). It runs on the server, so players can't change what it does.

  1. Publish the place, then in Studio open File → Experience Settings → Security and turn on Enable Studio Access to API Services (Roblox recommends a test copy for this).
  2. Insert a Script into ServerScriptService and paste the code. It creates leaderstats too, so remove any other leaderstats script.

The script

ServerScriptService > PlayerData (Script)
local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")

local store = DataStoreService:GetDataStore("PlayerData_v1")
local DEFAULTS = { Coins = 0, Level = 1 }

local function onPlayerAdded(player)
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player
	for name, default in DEFAULTS do
		local value = Instance.new("IntValue")
		value.Name = name
		value.Value = default
		value.Parent = leaderstats
	end

	local ok, saved = pcall(function()
		return store:GetAsync("player_" .. player.UserId)
	end)
	if not ok then
		-- Loading failed: don't save over the real data later.
		warn("Load failed for", player.Name, saved)
		return
	end
	if type(saved) == "table" then
		for name in DEFAULTS do
			if type(saved[name]) == "number" then
				leaderstats[name].Value = saved[name]
			end
		end
	end
	player:SetAttribute("DataLoaded", true)
end

local function save(player)
	if not player:GetAttribute("DataLoaded") then return end
	local data = {}
	for name in DEFAULTS do
		data[name] = player.leaderstats[name].Value
	end
	local ok, err = pcall(function()
		store:SetAsync("player_" .. player.UserId, data)
	end)
	if not ok then
		warn("Save failed for", player.Name, err)
	end
end

Players.PlayerAdded:Connect(onPlayerAdded)
for _, player in Players:GetPlayers() do
	task.spawn(onPlayerAdded, player)
end
Players.PlayerRemoving:Connect(save)

game:BindToClose(function()
	for _, player in Players:GetPlayers() do
		task.spawn(save, player)
	end
	task.wait(3) -- give the saves time to finish
end)

How it works

  • Each player's data is one table saved under the key player_<UserId>.
  • pcall catches errors: data store calls can fail when Roblox's servers are busy.
  • DataLoaded stops the script from saving default values over real data when loading failed.
  • BindToClose saves everyone when a server shuts down, which PlayerRemoving alone can miss.

Variations

Autosave every few minutes

Protects progress if a server crashes. Add this at the bottom of the script.

task.spawn(function()
	while true do
		task.wait(180)
		for _, player in Players:GetPlayers() do
			task.spawn(save, player)
		end
	end
end)

Adding a new stat later

Add it to DEFAULTS. Players with old saves get the default value, and it starts saving from then on — no reset needed.

Common mistakes

  • Testing in an unpublished place or without API access — saving silently fails and Output shows a warning.
  • Saving on every coin pickup. Data stores have request limits; save on leave, on shutdown and on a timer.
  • Renaming the data store (PlayerData_v1) after launch — players lose access to their old data.

FAQ

Why is my Roblox data store not saving?

The place must be published, Studio needs Enable Studio Access to API Services turned on, and the code must run in a server Script. Check Output for warnings.

Should I use SetAsync or UpdateAsync?

SetAsync is fine for one server saving one player's own data, as here. Use UpdateAsync when several servers might write the same key, such as trading or shared data.

Does data save in Studio?

Only with API access turned on, and it then writes to the real data store. Roblox recommends using a separate test place for that.

Keep reading