roblox-datastores — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited roblox-datastores (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Keep persistence on the server. For concurrent writes, use a pure UpdateAsync transform; after a write error, reconcile the backend state before retrying or compensating.
Official sources:
For temporary queues, lobbies, and caches, use MemoryStoreService. Read types-of-datastores.md when choosing a store.
Cross-reference:
→ Use MemoryStoreService (queues, sorted maps, etc.). DataStores are overkill and more expensive/slower. See references and official comparison.
→ Use OrderedDataStore (via GetOrderedDataStore). Values must be integers. No versioning/metadata. Special GetSortedAsync + DataStorePages iteration. Limits differ (see references/limits...).
ListKeysAsync, ListVersionsAsync, GetVersionAsync, GetVersionAtTimeAsync)?→ Use the full DataStore created via GetDataStore with DataStoreOptions (this returns the modern DataStore class). Preferred for serious player data.
→ Use DataStoreService:GetDataStore() (returns a standard DataStore) or DataStoreService:GetGlobalDataStore() (legacy global store, scope "u"). For new work, prefer a named GetDataStore store.
Scopes vs prefixes (modern recommendation): For new work, prefer key prefixes (e.g. "profiles/User_1234") + ListKeysAsync filtering over legacy scopes. Scopes prepend to keys automatically. Use DataStoreOptions.AllScopes=true + empty scope string for cross-scope listing (see references).
Studio access: If Studio needs backend APIs, use a dedicated test experience. Do not enable Studio sessions to read or write production data.
See references/best-practices-and-gotchas.md for full examples and variations (including session locking and profile patterns).
High-level server flow (in a ModuleScript required by ServerScriptService scripts):
playerData[player.UserId] = data).Use UpdateAsync with a pure (non-yielding) transform function for any value that can be concurrently modified by multiple servers. It reads-then-writes atomically from the perspective of the last writer.
Serialization rules (critical): Only nil, booleans, numbers (no inf/-inf/nan), strings (valid UTF-8), buffer, and tables of the above. No functions, no metatables on saved tables, no cycles. Use HttpService:JSONEncode on suspect data during development to preview what will actually be stored. See references/core-operations... .
Populate on relevant value changes or on save (use SetAsync or Increment). Query with GetSortedAsync(ascending, pageSize, min?, max?) → DataStorePages. Iterate pages with GetCurrentPage() + AdvanceToNextPageAsync(). For bulk reads, use BatchGetAsync(keys) to fetch multiple ordered entries in one request. Each key counts as one OrderedRead (see error-codes-and-limits for quotas; missing keys are omitted). Remember: only numbers; keys are strings; page iteration has its own limits; no versioning.
Display in a ScrollingFrame or via UI on demand. Cache top-N in a standard DataStore if you want fast global access without repeated queries.
See references for full page iteration example and limits differences.
Every Set/Update/Increment on a standard DataStore (not Ordered) creates hourly-versioned backups: the first write to a key in a given UTC hour creates a new version snapshot, and subsequent writes in the same hour overwrite that hourly version. Versioned backups expire ~30 days after being superseded; the current version never expires.
RemoveVersionAsync is deprecated. Do not present it as a current cleanup API; retain versions for recovery and use ListVersionsAsync, GetVersionAsync, or GetVersionAtTimeAsync to inspect them.Also use the daily Snapshot Data Stores Open Cloud API before risky publishes.
The Data Stores Manager in Creator Hub lets you browse keys, view metadata/version history, compare versions, and revert (with proper permissions).
See references/versioning-metadata-recovery.md for complete code samples including the "revert to time of incident" pattern.
By default: GetAsync results are cached locally for 4 seconds. Subsequent Gets within the window return cache and do not count against limits. Writes update the cache immediately.
For verification after writes (especially after errors), or when you suspect staleness due to other servers:
local opts = Instance.new("DataStoreGetOptions")
opts.UseCache = false
local value, info = store:GetAsync(key, opts)This always hits the backend and counts against quotas. Use sparingly but correctly.
See references/versioning-metadata-recovery.md (for listing, caching, serialization) and references/best-practices-and-gotchas.md .
There are experience-level limits (scale with concurrent users across the whole experience) and per-server limits (configurable via DataStoreService:SetRateLimitForRequestType). Experience-level pools are shared between game servers and Open Cloud v2 Data Store traffic. Bulk external jobs can throttle live players (and vice versa).
UpdateAsync counts against both read and write budgets.
Use DataStoreService:GetRequestBudgetForRequestType(Enum.DataStoreRequestType.XXX) to check before heavy operations and wait in a loop if necessary.
Queues exist (size 30); when full, you get throttle errors (301-306 range or the newer *Throttled errors).
Full tables of every error code (101 KeyNameEmpty ... through all the ExperienceThrottled and GameServerThrottled variants), experience formulas (base 300 + concurrentUsers × N), and server-side errors are in references/limits-quotas-throttling-error-codes.md .
Observability dashboard (Creator Hub → Monitoring → Data Stores) shows real-time request counts by API/status, quota usage percentages, storage bytes vs limit.
Data Stores Manager shows total size vs storage limit (500 MB + 1 MB × lifetime users, measured as compressed latest-version size), per-DS key counts, etc. Do not pre-compress values before writing.
Call SetRateLimitForRequestType early in server init (once per type) to raise limits for migration scripts or busy servers. Base + (perPlayer * numPlayers). Constraints per type are documented.
Every datastore call must be inside pcall.
On failure:
GetAsync using UseCache = false.IncrementAsync.committed, rejected, or unknown status so purchase/recovery code cannot mistake ambiguity for failure.Never assume a failed Set/Update/Increment means "no change occurred." UpdateAsync transforms can run multiple times and must not yield or produce external side effects.
RemoveAsync creates a "tombstone" (Get returns nil) but older versions remain queryable for 30 days (unless explicitly removed).
UpdateAsync from the Engine API or UpdateDataStoreEntry from Open Cloud.See references/versioning-metadata-recovery.md (covers listing/caching) and the manager/observability pages (linked in best-practices-and-gotchas.md).
Before any critical write:
On player join/load:
Before publishing risky data-logic changes:
Ongoing:
The scripts/ directory contains maturity-labeled examples:
SafeDataStore.lua: reviewed example with automatic read retries, explicit write states, opt-in idempotent replay, and uncached reconciliation.BudgetMonitor.lua: experimental budget-inspection example.Adapt and validate them in a dedicated test experience; this directory is not a versioned production library.
Combine this guidance with static analysis, focused tests, Studio integration, observability, and current official sources before trusting a persistence system.
For the authoritative class/property reference for any specific method, always cross-check https://create.roblox.com/docs/reference/engine (search for DataStoreService, GlobalDataStore, etc.).
<!-- catalog:references:start -->
<!-- catalog:references:end -->
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.