Roblox Datastores

sentinelcore/roblox-skills/roblox-datastores

by sentinelcoref2b1910a7fb898ed35cf2f856e2a2e48e38276bfNo licenseListed Oct 9, 2026Updated Oct 9, 2026

Use when implementing player data persistence in Roblox, saving/loading player stats or inventory, building leaderboards with ordered datastores, handling data migration between versions, diagnosing data loss issues, or adding auto-save and shutdown-safe data handling with DataStoreService.

Instructions onlySoftware Development
AI-generated overview

Reference guidance for Roblox DataStoreService: saving, loading, migrating and protecting player data on the server.

What it does
Provides a reference for Roblox DataStoreService covering GetAsync, SetAsync, UpdateAsync, RemoveAsync and ordered datastores. It shows patterns for loading and saving player data with pcall, retry logic, auto-save on PlayerRemoving, shutdown flushing with BindToClose, leaderboards, and versioned data migration. It also lists common mistakes such as missing pcall, unthrottled saves, and reusing datastore names after schema changes.
When to use it
Use when implementing player data persistence in Roblox, saving or loading player stats or inventory, building ordered-datastore leaderboards, migrating data between schema versions, or diagnosing data loss. Also relevant when adding auto-save and shutdown-safe handling.
Requirements
No scripts or assets ship with the skill; it is instructions only. It assumes a Roblox server environment with DataStoreService and Players services available, and API access enabled for the experience.

roblox-datastores

Reference for Roblox DataStoreService — saving, loading, and managing player data on the server.

Quick Reference

MethodSignatureNotes
GetDataStoreDSS:GetDataStore(name, scope?)Returns a GlobalDataStore
GetOrderedDataStoreDSS:GetOrderedDataStore(name, scope?)For leaderboards
GetAsyncstore:GetAsync(key)Returns value or nil
SetAsyncstore:SetAsync(key, value)No return value needed
UpdateAsyncstore:UpdateAsync(key, fn)Atomic read-modify-write
RemoveAsyncstore:RemoveAsync(key)Deletes key, returns old value
GetSortedAsyncorderedStore:GetSortedAsync(asc, pageSize)Returns DataStorePages

Basic Setup

lua
-- Server Script (ServerScriptService)local DataStoreService = game:GetService("DataStoreService")local Players = game:GetService("Players")
local playerStore = DataStoreService:GetDataStore("PlayerData_v1")
local DEFAULT_DATA = {    coins = 0,    level = 1,    xp = 0,}

Loading Data (GetAsync + pcall)

Always wrap datastore calls in pcall. They can fail due to network issues or rate limits.

lua
local function loadData(player)    local key = "player_" .. player.UserId    local success, data = pcall(function()        return playerStore:GetAsync(key)    end)
    if success then        local result = {}        for k, v in pairs(DEFAULT_DATA) do result[k] = v end        if data then            for k, v in pairs(data) do result[k] = v end        end        return result    else        warn("Failed to load data for", player.Name, ":", data)        return nil -- signal failure; do not give default data silently    endend

Saving Data (SetAsync vs UpdateAsync)

Use SetAsync for simple overwrites. Use UpdateAsync when the value must be based on the current stored value (e.g., incrementing a counter safely across servers).

lua
-- Simple savelocal function saveData(player, data)    local key = "player_" .. player.UserId    local success, err = pcall(function()        playerStore:SetAsync(key, data)    end)    if not success then        warn("Failed to save data for", player.Name, ":", err)    endend
-- Atomic increment with UpdateAsynclocal function addCoinsAtomic(userId, amount)    local key = "player_" .. userId    pcall(function()        playerStore:UpdateAsync(key, function(current)            current = current or { coins = 0 }            current.coins = current.coins + amount            return current        end)    end)end

Retry Logic

lua
local MAX_RETRIES = 3local RETRY_DELAY = 2
local function safeGet(store, key)    for attempt = 1, MAX_RETRIES do        local success, result = pcall(function()            return store:GetAsync(key)        end)        if success then return true, result end        warn(string.format("GetAsync attempt %d/%d failed: %s", attempt, MAX_RETRIES, result))        if attempt < MAX_RETRIES then task.wait(RETRY_DELAY) end    end    return false, nilend

Auto-Save: PlayerRemoving + BindToClose

Server shutdown without BindToClose silently discards unsaved data.

lua
local sessionData = {} -- [userId] = data table
Players.PlayerAdded:Connect(function(player)    local data = loadData(player)    if data then        sessionData[player.UserId] = data    else        player:Kick("Could not load your data. Please rejoin.")    endend)
Players.PlayerRemoving:Connect(function(player)    local data = sessionData[player.UserId]    if data then        saveData(player, data)        sessionData[player.UserId] = nil    endend)
-- Flush all sessions on server shutdowngame:BindToClose(function()    for userId, data in pairs(sessionData) do        local key = "player_" .. userId        pcall(function()            playerStore:SetAsync(key, data)        end)    endend)

Ordered DataStores (Leaderboards)

Values must be positive integers.

lua
local coinsLeaderboard = DataStoreService:GetOrderedDataStore("Coins_v1")
local function setLeaderboardScore(userId, coins)    pcall(function()        coinsLeaderboard:SetAsync("player_" .. userId, math.floor(coins))    end)end
local function getTopPlayers(count)    local success, pages = pcall(function()        return coinsLeaderboard:GetSortedAsync(false, count) -- false = descending    end)    if not success then return {} end
    local results = {}    for rank, entry in ipairs(pages:GetCurrentPage()) do        table.insert(results, { rank = rank, userId = entry.key, score = entry.value })    end    return resultsend

Data Versioning / Migration

Include a _version field and migrate in the load path.

lua
local CURRENT_VERSION = 2
local function migrateData(data)    local version = data._version or 1    if version < 2 then        data.coins = data.gold or 0  -- renamed field        data.gold = nil        data._version = 2    end    return dataend

Use a versioned datastore name (PlayerData_v2) for breaking schema changes.


Common Mistakes

MistakeConsequenceFix
No pcall around datastore callsUnhandled error crashes the scriptAlways wrap in pcall
Saving on every Changed eventHits rate limits (60 + numPlayers×10 writes/min)Throttle; save on remove + periodic interval
No BindToClose handlerData lost on server shutdownAlways flush all sessions in BindToClose
Giving default data on load failurePlayer silently loses progressReturn nil on failure; kick or retry
SetAsync for atomic countersRace condition across serversUse UpdateAsync for read-modify-write
Storing Instances or functionsData silently dropsStore only strings, numbers, booleans, plain tables
Reusing datastore name after schema changeOld shape clashes with new codeAppend _v2, _v3 to name on breaking changes

Source and attribution

Source:sentinelcore/roblox-skillsinroblox-datastoresat commitf2b1910

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal