CUBE BURSTSTUDIOS
leaderstats-saving.zip
Details

Leaderstats + Saving (DataStore)

Coins and Wins on the leaderboard, saved safely with UpdateAsync, retries, autosave and shutdown saving.

Category
Code
File
leaderstats-saving.zip
Size
2.2 KB
Downloads
0
Added
16 Oct 2026
Licence
Free to use, no credit needed (CC0)

#datastore#leaderstats#saving#coins#data#luau#roblox#script

⚑ Report this

Coins and Wins on the leaderboard, saved safely with UpdateAsync, retries, autosave and shutdown saving.

Shows Coins and Wins on the leaderboard and saves them safely.

SETUP
1. Put this Script in ServerScriptService.
2. Publish the game, then Game Settings > Security > turn ON "Enable Studio Access to API Services"
(only needed to test saving inside Studio).
3. Change other scripts' stats like this: player.leaderstats.Coins.Value += 10

WHY IT'S SAFE
- UpdateAsync (not SetAsync) so two servers never overwrite each other blindly.
- Retries with back-off when Roblox's DataStore service hiccups.
- Autosaves every 2 minutes and saves everyone when the server shuts down.
- Never saves a player whose data failed to load (so a bad load can't wipe their progress).

The main script is shown below with a Copy button, or grab the download with every file and a README.

SaveData.server.lua
--[[
	Leaderstats + Saving (DataStore)  ·  by Cube Burst Studios  ·  CC0
	Shows Coins and Wins on the leaderboard and saves them safely.

	SETUP
	1. Put this Script in ServerScriptService.
	2. Publish the game, then Game Settings > Security > turn ON "Enable Studio Access to API Services"
	   (only needed to test saving inside Studio).
	3. Change other scripts' stats like this:  player.leaderstats.Coins.Value += 10

	WHY IT'S SAFE
	- UpdateAsync (not SetAsync) so two servers never overwrite each other blindly.
	- Retries with back-off when Roblox's DataStore service hiccups.
	- Autosaves every 2 minutes and saves everyone when the server shuts down.
	- Never saves a player whose data failed to load (so a bad load can't wipe their progress).
]]

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local RunService = game:GetService("RunService")

local STORE = DataStoreService:GetDataStore("PlayerData_v1") -- change the name to reset everyone's data
local AUTOSAVE_SECONDS = 120
local MAX_TRIES = 4

-- every stat you want: name = starting value
local DEFAULTS = {
	Coins = 0,
	Wins = 0,
}

local loaded = {} -- [player] = true once their data loaded OK

local function withRetries(fn)
	local lastErr
	for attempt = 1, MAX_TRIES do
		local ok, result = pcall(fn)
		if ok then
			return true, result
		end
		lastErr = result
		task.wait(2 ^ attempt) -- 2, 4, 8, 16 seconds
	end
	return false, lastErr
end

local function keyFor(player)
	return "player_" .. player.UserId
end

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

	local ok, data = withRetries(function()
		return STORE:GetAsync(keyFor(player))
	end)
	if not ok then
		warn("Couldn't load data for " .. player.Name .. ": " .. tostring(data))
		return -- not marked loaded, so we will never save over their real data
	end
	if type(data) == "table" then
		for name in pairs(DEFAULTS) do
			if type(data[name]) == "number" then
				folder[name].Value = data[name]
			end
		end
	end
	loaded[player] = true
end

local function save(player)
	if not loaded[player] then
		return
	end
	local folder = player:FindFirstChild("leaderstats")
	if not folder then
		return
	end
	local snapshot = {}
	for name in pairs(DEFAULTS) do
		local value = folder:FindFirstChild(name)
		snapshot[name] = value and value.Value or DEFAULTS[name]
	end
	local ok, err = withRetries(function()
		STORE:UpdateAsync(keyFor(player), function()
			return snapshot
		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 ipairs(Players:GetPlayers()) do -- players who joined before this script ran
	task.spawn(load, player)
end

Players.PlayerRemoving:Connect(function(player)
	save(player)
	loaded[player] = nil
end)

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

game:BindToClose(function()
	if RunService:IsStudio() then
		task.wait(1)
	end
	local pending = 0
	for _, player in ipairs(Players:GetPlayers()) do
		pending += 1
		task.spawn(function()
			save(player)
			pending -= 1
		end)
	end
	local started = os.clock()
	while pending > 0 and os.clock() - started < 25 do
		task.wait(0.1)
	end
end)

MORE
CODE

View all ↗