Syntax
store:UpdateAsync(key, function(current)
-- Return the merged record, or nil to cancel. Do not yield.
end)Examples
Merge monotonic progress without overwriting newer data
ModuleScript named LessonSave in ServerScriptService of the test experience. It accepts only a ready session from the load flow. Invalid stored data cancels the update; it is never replaced with defaults. No rewards, waits or network calls belong inside the callback.
local store = game:GetService("DataStoreService"):GetDataStore("LessonProgress_TEST_v1")
local LessonSave = {}
local function valid(value)
return type(value) == "number" and value == value and value >= 0 and value <= 32 and value % 1 == 0
end
function LessonSave.save(userId, session)
if not session or not session.ready or not session.data or not valid(session.data.highestLesson) then
return false, "Session is not ready"
end
local highest = session.data.highestLesson -- snapshot before yielding
local ok, saved = pcall(function()
return store:UpdateAsync("player:" .. tostring(userId), function(current)
if current == nil then return {version = 1, highestLesson = highest} end
if type(current) ~= "table" or current.version ~= 1 or not valid(current.highestLesson) then return nil end
return {version = 1, highestLesson = math.max(current.highestLesson, highest)}
end)
end)
if not ok or saved == nil then return false, "Save failed or cancelled" end
return true
end
return LessonSaveWire an explicit test save
Script in ServerScriptService with LessonLoad and LessonSave from these pages. Use a dedicated test key only. Loading failure returns before saving. This saves unchanged loaded data to demonstrate the flow; a real progress increase must come from validated server gameplay.
local LessonLoad = require(script.Parent:WaitForChild("LessonLoad"))
local LessonSave = require(script.Parent:WaitForChild("LessonSave"))
local testUserId = 12345 -- dedicated test experience/key only
local data, reason = LessonLoad.load(testUserId)
if not data then warn(reason); return end
local session = {ready = true, data = data}
local ok, message = LessonSave.save(testUserId, session)
if not ok then warn(message) endBest practices
- UpdateAsync alone does not prevent stale session overwrites if you return a stale full snapshot; choose a deliberate merge or session-locking strategy.
- Keep callbacks deterministic and free of side effects because they can run more than once.
- This teaching schema has no deletion/reset, trading, currency or session-lock protocol. Do not extend max merging to those operations.
At a glance
- Purpose
- Typed scripting and Roblox development
- File extension
- .luau
- Runs in
- Luau host; Roblox engine examples require Roblox Studio
- Usually used with
- Roblox APIs and Studio
Specifications & further reading
Related Luau documentation
A successful read returning nil means no stored value exists. A failed read means the value is unknown. Those cases must never share a fallback that later saves defaults over real data. Validate stored fields before marking a session ready.Retries, autosave and shutdown limits
A save can fail after gameplay succeeded. Keep the ready session available for another attempt, retry only a bounded number of times and report unresolved failures. PlayerRemoving and BindToClose are opportunities to save, not guarantees of durability.Server authority, validation and rate limits
A valid-looking remote call can still be an impossible game action. Validate both the shape of a request and whether the sender may perform it now. This example lets a nearby living player request a visual switch; it does not trust a client position, target Instance or reward.DataStore setup and safe Studio testing
DataStoreService persists data across sessions and is accessed by server scripts. Studio API access can reach real experience data. Use a separate test experience and test store names before enabling access; these examples are learning exercises, not a complete production persistence framework.