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
| When | What happens |
|---|---|
| The value appears | Its 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 playing | A change is written within about 30 seconds. |
| The player leaves, or the server stops | Written immediately. |
| A saved value could not be loaded | It 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
leaderstatswith 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
leaderstatsand 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
| Method | Returns | Description |
|---|---|---|
DataStoreService:GetDataStore(name) | DataStore | Opens a store by name. |
store:GetAsync(key) | number or nil | Reads a key. nil if nothing was saved. |
store:SetAsync(key, number) | Writes a key. The last writer wins. | |
store:IncrementAsync(key, delta) | number | Adds 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 nil | Deletes 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
NaNis an error. - Saved data belongs to the game. All of its servers share it and it survives restarts.
- The
...Asynccalls wait for the website. Call them from a thread that may wait (an event handler or your main script), not in a tightRunService.Frameloop. A request that gets no answer fails with an error after 20 seconds, so wrap calls inpcallif you want to survive a failure.
Limits
| What | Limit |
|---|---|
| Requests | 600 per minute per game server, 64 waiting at once |
| Store name | up to 50 characters |
| Key | up to 100 characters |
| Keys per game | 200,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
