CyberMax
Home › Blog

How to save Roblox player data without losing it: session locking, receipts and retries

1 October 2026 · by CyberMax · 7 min read

Most lost-progress reports in Roblox games come from a handful of server situations, not from Roblox "deleting" data. A player leaves one server and joins another before the first has finished saving. A DataStore call fails during an outage and the error is ignored. A developer-product purchase is granted, the save fails, and Roblox calls the receipt handler again. Each one has a known fix. Here they are in the order they bite.

1. Two servers, one save: lock the session

When a player teleports or rejoins quickly, the old server may still be writing their data while the new server is loading it. If the new server loads the old copy, plays for ten minutes and saves, the old server's last write is lost, or the other way round.

The fix is a session lock: the save itself records which server owns it and when that server last touched it.

  1. On join, use UpdateAsync (never SetAsync) to read the save and, in the same transform, write your server's JobId and the current time into it, but only if no other server holds a fresh lock.
  2. If another server holds a fresh lock, wait a few seconds and retry; if the lock is stale (that server crashed and has not touched it for several minutes), take it over.
  3. Every autosave and the final save on leave also go through UpdateAsync and check that your server still owns the lock. If it does not, stop saving: another server owns the player now.
  4. On leave, write the data and clear the lock in the same update.

UpdateAsync matters because it reads and writes in one step, so two servers cannot both believe they own the save.

2. Outages and budgets: retry, then stop the player from losing more

DataStore requests can fail (service hiccups, request budgets). Wrap every call in pcall, retry with a short backoff a few times, and decide what happens if it still fails. On join, a failed load must not fall back to an empty profile that later overwrites the real one: kick the player with a friendly message or keep the session read-only. On leave, keep retrying the save while the server is alive and save everyone in game:BindToClose.

3. Robux purchases: grant once, and only after it is saved

MarketplaceService.ProcessReceipt is called for every developer-product purchase. Roblox keeps calling it (for example when the player rejoins) until you return PurchaseGranted (an Enum.ProductPurchaseDecision value). Two rules follow:

4. Never trust the client

Every RemoteEvent argument can be forged by an exploiter. Check types and ranges on the server (an item id is a short string, an amount is an integer from 1 to 99), rate-limit each remote per player, and keep prices, rewards and cooldowns on the server.

How Studwright tests this

Studwright is our kit of 16 Luau modules that implement these patterns. Its 66 tests run in a real Luau VM against a mock of the Roblox engine with a virtual clock, outage injection and a request budget. They cover two servers fighting over one save, a stale lock being taken over, a server that lost its lock and must stop saving, retried and failed receipts, 100,000 seeded loot rolls and the shipped example server end to end. The tests ship inside the zip, so you can run them yourself.

Using it looks like this:

local store = SaveData.new({ name = "PlayerData_v1", template = { Coins = 0, Level = 1 } })
local profile = store:Load(player)          -- session-locked, retried; nil if the save is busy
local codes = Codes.new({ RELEASE = { reward = function(p) p.Data.Coins += 500 end } })
local ok, message = codes:Redeem(profile, textFromClient)

Run the free Studwright modules in your browser

Make the UI look finished: 9-slice images

Roblox buttons and panels look sharp at every size when their image is 9-sliced: set ScaleType to Slice and SliceCenter to the rectangle that may stretch, so the corners keep their shape. Three habits save time:

Glintpane, our UI kit, has 77 icons in colour and white, two atlases that hold every icon (two uploads), 50 9-slice pieces and a Luau module that builds buttons and panels with the right SliceCenter.

Roblox is a trademark of Roblox Corporation. CyberMax is an independent developer; these kits use only Roblox's public APIs.

More from the CyberMax blog