All skills
gamedev-skills avatar

/roblox-datastores

@3727d02

Persist player data in Roblox with DataStoreService: GetDataStore, GetAsync/ SetAsync/UpdateAsync/IncrementAsync wrapped in pcall, load-on-join and save-on-leave plus BindToClose, retries, and OrderedDataStore leaderboards. Use when saving or loading persistent data in a Roblox experience — when the user mentions DataStore, DataStoreService, GetAsync, SetAsync, UpdateAsync, save player data, or leaderboards. For general Luau scripting use roblox-luau.

Use this Skill: https://skilld.dev/gh/gamedev-skills/awesome-gamedev-agent-skills/roblox-datastores

This session only. Nothing lands on disk.

referencessessions-and-limits.md

≈1.4k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Roblox DataStores: sessions, limits & metadata (current engine)

Depth behind the DataStores skill: session locking, versioning/metadata, ordered- store pagination, request limits, and compliance.

Session locking (preventing duplicate / clobbered data)

Players can occasionally be in two servers briefly (teleports, rejoin races). Two servers loading and later saving the same key can lose writes. A session lock makes a key owned by one server at a time:

  1. On load, UpdateAsync the key to stamp it with this server's JobId and a timestamp, but only if it's unlocked or the lock is stale (older than, say, the BindToClose budget).
  2. If it's locked by a live server, retry a few times, then either kick the player or load read-only.
  3. On save/leave, clear the lock as part of the same UpdateAsync.

UpdateAsync is the right primitive because its callback sees the current value, so the check-and-set is atomic per request. (Production code often uses a vetted open-source profile/session library that implements this correctly; if you roll your own, test the teleport and rapid-rejoin cases.)

Versioning & metadata

Regular DataStore keys support versioning and metadata; OrderedDataStore does not (DataStoreKeyInfo is always nil there).

local DataStoreService = game:GetService("DataStoreService")
local store = DataStoreService:GetDataStore("PlayerData")

-- Attach UserIds (for content tracking) and custom metadata on write.
local options = Instance.new("DataStoreSetOptions")
options:SetMetadata({ schema = 2, region = "eu" })
pcall(function()
    store:SetAsync(key, data, { player.UserId }, options)
end)

-- Read returns a second value, the DataStoreKeyInfo.
local ok, value, keyInfo = pcall(function() return store:GetAsync(key) end)
if ok and keyInfo then
    print(keyInfo.Version, keyInfo.CreatedTime, keyInfo.UpdatedTime)
    print(keyInfo:GetUserIds(), keyInfo:GetMetadata())
end

Important: when you set metadata or UserIds, you must re-supply them on every write or they're cleared. List past versions with ListVersionsAsync and fetch one with GetVersionAsync to recover from a bad write.

A schema/version number stored alongside the data lets you migrate old saves: on load, if data.schema < CURRENT, transform the table forward before use.

Ordered stores: full pagination

local boards = DataStoreService:GetOrderedDataStore("Coins")
local ok, pages = pcall(function()
    return boards:GetSortedAsync(false, 50)   -- descending, page size 50
end)
if ok then
    while true do
        for rank, entry in ipairs(pages:GetCurrentPage()) do
            -- entry.key (the data key), entry.value (the number)
        end
        if pages.IsFinished then break end
        pages:AdvanceToNextPageAsync()        -- yields; wrap loops in pcall in production
    end
end

GetSortedAsync(ascending, pageSize, minValue?, maxValue?) — page size caps how many entries AdvanceToNextPageAsync fetches per request.

Request limits & throttling

  • Requests draw on two tiers of per-minute budget, both refreshed continuously: an EXPERIENCE-level (game-wide) budget that scales with total concurrent users across the whole experience, and a per-SERVER budget that scales with the players in that one server. Since 2026-07-29 the in-game (game server) and Open Cloud APIs SHARE the experience-level budget, so heavy external Open Cloud traffic can throttle in-game requests and vice versa. Experience limits start at a 300/min baseline plus a per-CCU multiplier per request type (reads scale fastest, lists slowest); UpdateAsync spends from BOTH the read and write budgets on every call. Exceeding a budget queues requests (each queue holds ~30) and then drops them with a 301-306 error. Per-server defaults are configurable via DataStoreService:SetRateLimitForRequestType; check current headroom with GetRequestBudgetForRequestType.
  • Storage is a GAME-level limit, not per-key: total = 500 MB + 1 MB x lifetime user count (any user who has ever joined), measured on the compressed latest version of each key. Superseded versions and deleted keys do not count toward it.
  • GetAsync results are cached for a short window — an immediate re-read returns the cached value, not necessarily the freshest. Disable caching only if you truly need to (it costs extra requests).
  • Practical rules: save on leave / BindToClose and on a periodic timer (e.g. every 60–120s), not on every value change. Coalesce many small changes into one write. Use UpdateAsync so concurrent writers don't clobber.
  • Treat every Async call as fallible: pcall + bounded retry with backoff. See https://create.roblox.com/docs/cloud-services/data-stores/error-codes-and-limits for the exact current numeric limits, which change over time.

Right to be forgotten (RTBF)

You're responsible for deleting a user's data on request. Implement a removal path (e.g. RemoveAsync on every store keyed by that UserId, plus any ordered-store entries) and the Open Cloud DataStore API can perform deletions from outside the game. Keep the list of stores/keys a player touches so deletion is complete.

Open Cloud (out-of-game access)

The Open Cloud DataStore API lets trusted external tools read/write the same stores with scoped API keys — useful for admin dashboards, moderation, and bulk migration. Treat the API key as a secret; never embed it in client or game code.

Source: SKILL.md on GitHub

No alerts5d3 checks · Risk SAFE
  • Gen Agent Trust Hub5d

    This skill provides best-practice templates and guidance for Roblox DataStores. It focuses on safe data persistence, error handling with pcalls, and preventing data loss using UpdateAsync and BindToClose. No security risks were identified.

  • Socket5d

    No alerts

  • Snyk5d

    Risk: LOW · No issues

Signed by skilld at 3727d02. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 2 months ago

README badge

README badge for gamedev-skills/awesome-gamedev-agent-skills/roblox-datastores