Skip to content

Saving data ​

data keeps values between sessions: a player's coins, their inventory, the best time on a course. It is for server Scripts only.

A store ​

lua
local store = data.store("PlayerData")   -- a named store, one per game
MethodWhat it does
store:get(key)The value saved under key, or nil
store:set(key, value)Saves value under key (nil removes it)
store:update(key, fn)Reads the value, passes it to fn, saves what fn returns
store:increment(key, delta)Adds delta (1 if left out) to a number and returns the new total
store:remove(key)Removes the key

Values can be nil, booleans, numbers, strings, value types (vec3, colours...) and tables of those - no parts or objects - up to 16 KB a key.

Each call waits for the answer without freezing the game, and raises an error when it fails (the network, the database). Always wrap it in pcall.

Saving a player's coins ​

lua
local store = data.store("PlayerData")

players.joined:connect(function(player)
    player.stats.Coins = 0                       -- before anything was saved
    if player.userId <= 0 then return end        -- guests (userId -1) are never saved

    local ok, saved = pcall(function()
        return store:get("user_" .. player.userId)
    end)
    if ok and saved then
        player.stats.Coins = saved
    end
end)

players.left:connect(function(player)
    if player.userId <= 0 then return end
    pcall(function()
        store:set("user_" .. player.userId, player.stats.Coins)
    end)
end)

Type savedata at the start of a line in the editor to get this snippet.

Key by userId, never by name

Players can change their name. Their userId stays the same for life. Guests have userId -1, so check userId > 0 before saving.

Saving a table ​

lua
local profile = {
    coins = 120,
    level = 4,
    inventory = {"Sword", "Shield"},
}
pcall(function() store:set("user_" .. player.userId, profile) end)

Save one table per player rather than many keys: one request instead of several, and it can't be half-saved.

Safe changes: update and increment ​

update and increment are safe when several servers touch the same key at once (two servers of the same game, a global counter). update only writes over the value it read; if it changed meanwhile, it reads again and runs fn again - so keep fn free of side effects:

lua
local stats = data.store("Global")
pcall(function() stats:increment("totalWins") end)

pcall(function()
    stats:update("bestTime", function(old)
        if old == nil or myTime < old then return myTime end
        return old
    end)
end)

When the server shuts down ​

When a server closes - an update, a restart, the last player leaving - game.closing fires first, with every player still in the game, then every player leaves, so your players.left handlers run and save. The server waits for them up to 20 seconds.

lua
game.closing:connect(function()
    -- save anything the game keeps for everyone (a world record, a shared bank)
end)

Read before you wait

After a task.wait inside players.left, the player is gone: read what you need (player.userId, player.stats.Coins) before waiting.

Where it is kept ​

Games started from KHIM save to KHIM, per game: another game can't read them.

Limits ​

  • 16 KB a value.
  • 64 requests in flight at once on one server.
  • update gives up with an error if the key keeps changing twelve times in a row.

JSON ​

json.encode(value, pretty?) turns a table into JSON text, json.decode(text) turns it back - for settings written as JSON, or a table saved as one string:

lua
local text = json.encode({coins = 5, items = {"Sword"}})   -- '{"coins":5,"items":["Sword"]}'
local back = json.decode(text)
print(back.items[1])                                       -- Sword