Saving data

Studz saves numbers for your game, in two ways: automatically through leaderstats, or by hand with DataStoreService. Only server Scripts can use either.

leaderstats: the easy way

A number value (IntValue or NumberValue) inside a folder named leaderstats under a player does two things with no extra code. It appears in the HUD's player list, and it is saved.

Players.PlayerAdded:On(function(player)
    local stats = Instance.Create("Folder", { Name = "leaderstats", Parent = player })
    Instance.Create("IntValue", { Name = "Coins", Value = 0, Parent = stats })
end)

How it saves

WhenWhat happens
The value appearsIts starting number is replaced by the saved one. A new player keeps the starting number. Anything a script added while the load was still running counts on top of the saved number.
While playingA change is written within about 30 seconds.
The player leaves, or the server stopsWritten immediately.
A saved value could not be loadedIt is not saved over, so a website hiccup never resets a player to zero. The load is retried a few times and a warning appears in the Output.

Things to know

  • The player list shows one number per player. With several values in leaderstats, the one changed last is shown.
  • Values are saved under the store leaderstats with the key <user id>:<value name>. You can read them yourself: DataStoreService:GetDataStore("leaderstats"):GetAsync(player.UserId .. ":Coins").
  • Players:SetStatsSaving(false) turns automatic saving off. The player list still works.
  • A leaving player's leaderstats and anything else parented to them are removed from the server.

DataStoreService: the manual way

A data store has a name, and each key in it holds one number: a coin count, a high score, a level.

local DataStoreService = game:Service("DataStoreService")
local bank = DataStoreService:GetDataStore("Bank")

bank:SetAsync(player, 250)                  -- a player is a key (their user id)
local coins = bank:GetAsync(player)         -- 250, or nil if nothing was saved
bank:IncrementAsync(player, 10)             -- add 10 (a missing key counts as 0) -> 260
bank:RemoveAsync(player)                    -- forget it; returns what was saved
bank:SetAsync("globalRecord", 9001)         -- any string works as a key too

Methods

MethodReturnsDescription
DataStoreService:GetDataStore(name)DataStoreOpens a store by name.
store:GetAsync(key)number or nilReads a key. nil if nothing was saved.
store:SetAsync(key, number)Writes a key. The last writer wins.
store:IncrementAsync(key, delta)numberAdds delta and returns the new total. A missing key counts as 0. Atomic: two servers adding at once do not lose an update.
store:RemoveAsync(key)number or nilDeletes the key and returns what was saved.

Rules

  • Keys are strings, numbers or players. A player key means their user id.
  • Values are numbers only: finite, up to 9e15 so whole numbers stay exact. Saving a string, a table or NaN is an error.
  • Saved data belongs to the game. All of its servers share it and it survives restarts.
  • The ...Async calls wait for the website. Call them from a thread that may wait (an event handler or your main script), not in a tight RunService.Frame loop. A request that gets no answer fails with an error after 20 seconds, so wrap calls in pcall if you want to survive a failure.

Limits

WhatLimit
Requests600 per minute per game server, 64 waiting at once
Store nameup to 50 characters
Keyup to 100 characters
Keys per game200,000

In Polycarbonate

During playtests the numbers are kept in memory for the session only. Scripts behave the same, but nothing is saved between runs. The single-process Play (F5) playtest does not save leaderstats at all.

Saving something that is not a number

Stores hold numbers only. To save a set of choices, give each one its own key:

local unlocks = DataStoreService:GetDataStore("Unlocks")
unlocks:SetAsync(player.UserId .. ":redSkin", 1)    -- 1 = unlocked
local hasRedSkin = unlocks:GetAsync(player.UserId .. ":redSkin") == 1