User avatar
Site Admin
Roblox DataStore: saving player progress with SetAsync and GetAsync
Roblox DataStore: saving player progress with SetAsync and GetAsync

If you have ever built a Roblox game where players lose all their progress when they leave, you need DataStore. It is the built-in service for saving and loading persistent player data across sessions. This guide covers the basics of creating a data store, saving player data, and reading it back when the player joins again.

Enabling Studio access

By default, games tested in Studio cannot access data stores. You need to enable it before you can test your save system. Publish your game first, then open Studio and go to File, then Experience Settings. In the Security section, turn on "Enable Studio Access to API Services" and click Save. Without this step, every data store call will fail in Studio.

Creating a data store

Data stores are managed by DataStoreService. You get a reference to a specific store by calling [icode]GetDataStore()[/icode] with a unique name. If the store does not exist, it gets created automatically.

```lua
local DataStoreService = game:GetService("DataStoreService")
local goldStore = DataStoreService:GetDataStore("PlayerGold")
```

Store names are global to your experience. Two different games can use the same store name without conflict, but within one game, always reference the same name for the same data.

Saving and loading data

A data store works like a dictionary. Each value is indexed by a unique key, which is usually the player's UserId.

To save data, call [icode]SetAsync()[/icode] inside a protected call. If the request fails, the pcall catches the error instead of crashing the script.

```lua
local success, err = pcall(function()
goldStore:SetAsync(userId, playerGold)
end)

if not success then
warn(err)
end
```

To load data, call [icode]GetAsync()[/icode] when the player joins.

```lua
local success, data = pcall(function()
return goldStore:GetAsync(userId)
end)

if success and data then
playerGold = data
else
playerGold = 0
end
```

Automatic saving and throttling

Data stores have a request budget. If you exceed it, additional requests are throttled and dropped. The simplest way to avoid throttling is to save at reasonable intervals instead of on every change.

A common pattern is to save every 30 seconds and also save when the player leaves.

```lua
-- Auto-save every 30 seconds
while true do
wait(30)
for _, player in ipairs(Players:GetPlayers()) do
local userId = player.UserId
local gold = leaderstats["Gold"].Value
pcall(function()
goldStore:SetAsync(userId, gold)
end)
end
end
```

```lua
-- Save when player leaves
Players.PlayerRemoving:Connect(function(player)
local userId = player.UserId
local gold = leaderstats["Gold"].Value
pcall(function()
goldStore:SetAsync(userId, gold)
end)
end)
```

Requests are placed in a queue. If the queue fills up, additional requests are dropped silently. This is why you should never save on every single change and always use pcall.

Designing your data schema

Store one dictionary per player instead of multiple keys for the same player. This is cleaner and avoids partial updates when one request fails.

A simple schema might look like this:

```lua
local PlayerDataSchema = {
General = {
Currency = 0,
Experience = 0
},
Moderation = {
AdminStatus = "Normal",
ModerationStatus = "None"
},
PurchaseLog = {}
}
```

When the player joins, check if the returned data matches the schema. If it is empty or missing fields, initialize it with defaults.

Common mistakes

Not using pcall is the most common error. Data store requests can fail for network reasons, budget limits, or server issues. Without pcall, a failed request crashes your script and the player loses data.

Saving too frequently is another one. If you save every time a currency value changes and the player is earning quickly, you will hit the request budget and lose saves. Batch your saves at intervals.

Storing data across multiple keys creates race conditions. If one SetAsync succeeds and another fails because of budget, the player ends up with inconsistent data. One dictionary per player avoids this.

Sources
[1] Roblox Creator Docs: Save player data with standard data stores
[2] Roblox Creator Docs: Save data (intermediate scripting)