Data Stores

DataStoreService lets you store data that needs to persist between sessions, such as items in a player's inventory or skill points. Data stores are consistent per experience, so any place in an experience can access and change the same data, including places on different servers.

If you want to add granular permission control to your data stores and access them outside of Studio or Roblox servers, you can use DataStore API through Roblox Open Cloud.

Enabling Studio Access

By default, experiences tested in Studio cannot access data stores, so you must first enable them. Accessing data stores in Studio can be dangerous for live experiences because Studio accesses the same data stores as the client application. To avoid overwriting production data, you should not enable this setting for live experiences — instead, enable it for a separate test version of the experience.

To enable Studio access in a published experience:

  1. In the Home tab of the menu bar, navigate to the Settings section and click Game Settings. The Game Settings menu displays.
  2. In the left-hand navigation of the Game Settings menu, click Security.
  3. Enable the Enable Studio Access to API Services toggle.
  4. Click the Save button.

Accessing a Data Store

Once you include DataStoreService in a script, access a data store by name using the GetDataStore() function. For example:


1local DataStoreService = game:GetService("DataStoreService")
2
3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
4

Scopes

Every key in a data store has a default "global" scope, but you can further organize keys by setting a unique string as a scope for the second parameter of GetDataStore(). This automatically prepends the scope to all keys in all operations done on the data store.

The scope categorizes your data with a string and a separator with "/", such as:

Key Scope
houses/User_1234 houses
pets/User_1234 pets
inventory/User_1234 inventory

The combination of datastore name + scope + key uniquely identifies a key and all three values are required to identify a key if it has a scope. For example, a global key named User_1234 can be read as follows:


1local DataStoreService = game:GetService("DataStoreService")
2local inventoryStore = DataStoreService:GetDataStore("PlayerInventory")
3local success, currentGold = pcall(function()
4 return inventoryStore:GetAsync("User_1234")
5end)
6

By contrast, if key ``User_1234` has a scope of gold, you can only read it as:


1local DataStoreService = game:GetService("DataStoreService")
2local inventoryStore = DataStoreService:GetDataStore("PlayerInventory", "gold")
3local success, currentGold = pcall(function()
4 return inventoryStore:GetAsync("User_1234")
5end)
6

Managing a Data Store

Setting Data

A data store is essentially a dictionary, similar to a Lua table. A unique key indexes each value in the data store, such as a player's unique Player.UserId or a named string for a game promo.

Key Value
31250608 50
351675979 20
505306092 78000
Player Data
Key Value
ActiveSpecialEvent SummerParty2
ActivePromoCode BONUS123
CanAccessPartyPlace true
Promo Data

To create a new entry, call SetAsync() with the key name and a value.


1local DataStoreService = game:GetService("DataStoreService")
2
3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
4
5local success, errorMessage = pcall(function()
6 experienceStore:SetAsync("User_1234", 50)
7end)
8if not success then
9 print(errorMessage)
10end
11

Reading Data

The GetAsync() function reads the value of a data store entry. It requires just the key name of the entry.


1local DataStoreService = game:GetService("DataStoreService")
2
3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
4
5local success, currentExperience = pcall(function()
6 return experienceStore:GetAsync("User_1234")
7end)
8if success then
9 print(currentExperience)
10end
11

Incrementing Data

IncrementAsync() changes a numerical value in a data store. This function requires the key name of the entry and a number indicating how much to change the value.


1local DataStoreService = game:GetService("DataStoreService")
2
3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
4
5local success, newExperience = pcall(function()
6 return experienceStore:IncrementAsync("Player_1234", 1)
7end)
8if success then
9 print(newExperience)
10end
11

Updating Data

UpdateAsync() changes any stored value in a data store. This function requires the key name of the entry plus a callback function which defines how the entry should be updated. This callback takes the current value and returns the new value, based on whatever logic you define. If the callback returns nil, the write operation is cancelled and the value remains unchanged.


1local DataStoreService = game:GetService("DataStoreService")
2
3local nicknameStore = DataStoreService:GetDataStore("Nicknames")
4
5local function makeNameUpper(currentName)
6 local nameUpper = string.upper(currentName)
7 return nameUpper
8end
9
10local success, updatedName = pcall(function()
11 return nicknameStore:UpdateAsync("User_1234", makeNameUpper)
12end)
13if success then
14 print("Uppercase Name:", updatedName)
15end
16

Set vs. Update

SetAsync() is best for a quick update of a specific key, and it only counts against the write limit. However, it may cause data inconsistency if two servers attempt to set the same key at the same time.

UpdateAsync() is safer for handling multi-server attempts because it reads the current key value from the server that last updated it before making any changes. However, it's somewhat slower because it reads before it writes, and it also counts against both the read and write limit.

Removing Data

RemoveAsync() removes an entry and returns the value that associates with the key.


1local DataStoreService = game:GetService("DataStoreService")
2
3local nicknameStore = DataStoreService:GetDataStore("Nicknames")
4
5local success, removedValue = pcall(function()
6 return nicknameStore:RemoveAsync("User_1234")
7end)
8if success then
9 print(removedValue)
10end
11

Ordered Data Stores

By default, data stores do not sort their content, but sometimes it's necessary to get data in an ordered fashion, such as persistent leaderboard stats. You can achieve this by calling GetOrderedDataStore() instead of GetDataStore().


1local DataStoreService = game:GetService("DataStoreService")
2
3local characterAgeStore = DataStoreService:GetOrderedDataStore("CharacterAges")
4

Ordered data stores support the same basic functions as default data stores, plus the unique GetSortedAsync() function. This retrieves multiple sorted keys based on a specific sorting order, page size, and minimum/maximum values.

The following example sorts character data into pages with three entries each in descending order, then loops through the pages and outputs each character's name/age.


1local DataStoreService = game:GetService("DataStoreService")
2
3local characterAgeStore = DataStoreService:GetOrderedDataStore("CharacterAges")
4
5-- Populate ordered data store
6local characters = {
7 Mars = 19,
8 Janus = 20,
9 Diana = 18,
10 Venus = 25,
11 Neptune = 62
12}
13for char, age in pairs(characters) do
14 local success, errorMessage = pcall(function()
15 characterAgeStore:SetAsync(char, age)
16 end)
17 if not success then
18 print(errorMessage)
19 end
20end
21
22-- Sort data by descending order into pages of three entries each
23local success, pages = pcall(function()
24 return characterAgeStore:GetSortedAsync(false, 3)
25end)
26if success then
27 while true do
28 -- Get the current (first) page
29 local entries = pages:GetCurrentPage()
30 -- Iterate through all key-value pairs on page
31 for _, entry in pairs(data) do
32 print(entry.key .. " : " .. tostring(entry.value))
33 end
34 -- Check if last page has been reached
35 if pages.IsFinished then
36 break
37 else
38 print("----------")
39 -- Advance to next page
40 pages:AdvanceToNextPageAsync()
41 end
42 end
43end
44

Metadata

There are two types of metadata associated with keys:

  • Service-defined - Every object has default read-only metadata such as the most recent update time and creation time.
  • User-defined - Through the DataStoreSetOptions object and its SetMetadata() function, you can include custom metadata for tagging and categorization.

Metadata is managed by expanding the SetAsync(), GetAsync(), UpdateAsync(), IncrementAsync(), and RemoveAsync() functions.

  • SetAsync() accepts optional third and fourth arguments:

    • Table of UserIds, highly recommended to assist with content copyright and intellectual property tracking/removal.

    • A DataStoreSetOptions object on which you can define custom metadata using its SetMetadata() function.


      1local DataStoreService = game:GetService("DataStoreService")
      2
      3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
      4
      5local setOptions = Instance.new("DataStoreSetOptions")
      6setOptions:SetMetadata({["ExperienceElement"] = "Fire"})
      7
      8local success, errorMessage = pcall(function()
      9 experienceStore:SetAsync("User_1234", 50, {1234}, setOptions)
      10end)
      11if not success then
      12 print(errorMessage)
      13end
      14
  • GetAsync(), IncrementAsync(), and RemoveAsync() return a second value (DataStoreKeyInfo object) that contains both service-defined properties and functions to fetch user-defined metadata:


    1local DataStoreService = game:GetService("DataStoreService")
    2
    3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
    4
    5local success, currentExperience, keyInfo = pcall(function()
    6 return experienceStore:GetAsync("User_1234")
    7end)
    8if success then
    9 print(currentExperience)
    10 print(keyInfo.Version)
    11 print(keyInfo.CreatedTime)
    12 print(keyInfo.UpdatedTime)
    13 print(keyInfo:GetUserIds())
    14 print(keyInfo:GetMetadata())
    15end
    16
  • The callback function of UpdateAsync() takes an additional parameter (DataStoreKeyInfo object) that describes the current key state. It returns the modified value, the key's associated UserIds, and the key's metadata.


    1local DataStoreService = game:GetService("DataStoreService")
    2
    3local nicknameStore = DataStoreService:GetDataStore("Nicknames")
    4
    5local function makeNameUpper(currentName, keyInfo)
    6 local nameUpper = string.upper(currentName)
    7 local userIDs = keyInfo:GetUserIds()
    8 local metadata = keyInfo:GetMetadata()
    9 return nameUpper, userIDs, metadata
    10end
    11
    12local success, updatedName, keyInfo = pcall(function()
    13 return nicknameStore:UpdateAsync("User_1234", makeNameUpper)
    14end)
    15if success then
    16 print(updatedName)
    17 print(keyInfo.Version)
    18 print(keyInfo.CreatedTime)
    19 print(keyInfo.UpdatedTime)
    20 print(keyInfo:GetUserIds())
    21 print(keyInfo:GetMetadata())
    22end
    23

User-defined metadata has the following limits:

  • Key length: up to 50 characters.
  • Value length: up to 250 characters.
  • No limit for the total number of key-value pairs but the total size cannot exceed 300 characters.

Versioning

With versioning, SetAsync() and UpdateAsync() create new versions instead of overwriting existing data, and GetAsync() reads the latest version. DataStoreService periodically checks the timestamps of each version and removes versions older than 30 days, but retains the latest version indefinitely.

There are three new APIs for versioning operations:

Function Description
ListVersionsAsync() Lists all versions for a key by returning a DataStoreVersionPages instance that you can use to enumerate all version numbers. You can filter versions based on a time range as shown in the code example below.
GetVersionAsync() Retrieves a specific version of a key using its version number.
RemoveVersionAsync() Deletes a specific version of a key.

Versioning is convenient for user support. For example, if a user reports that a problem occurred at 2020-10-09T01:42, you can revert to a previous version using the code below.


1local DataStoreService = game:GetService("DataStoreService")
2
3local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
4
5local DATA_STORE_KEY = "User_1234"
6
7local maxDate = DateTime.fromUniversalTime(2020, 10, 09, 01, 42)
8
9-- Get the version closest to the given time
10local listSuccess, pages = pcall(function()
11 return experienceStore:ListVersionsAsync(DATA_STORE_KEY, Enum.SortDirection.Descending, nil, maxDate.UnixTimestampMillis)
12end)
13if listSuccess then
14 local items = pages:GetCurrentPage()
15 if table.getn(items) > 0 then
16 -- Read the closest version
17 local closestEntry = items[1]
18 local success, value, info = pcall(function()
19 return experienceStore:GetVersionAsync(DATA_STORE_KEY, closestEntry.Version)
20 end)
21 -- Restore current value by overwriting with the closest version
22 if success then
23 local setOptions = Instance.new("DataStoreSetOptions")
24 setOptions:SetMetadata(info:GetMetadata())
25 experienceStore:SetAsync(DATA_STORE_KEY, value, nil, setOptions)
26 end
27 else
28 -- No entries found
29 end
30end
31

Listing and Prefixes

Data stores allow for listing by prefix (the first n characters of a name, such as "d", "do", or "dog" for any key or data store with a prefix of "dog").

You can specify a prefix when listing all data stores or keys, and only objects matching that prefix will be returned. Both functions return a DataStoreListingPages object that you can use to enumerate the list.

Function Description
ListDataStoresAsync() Lists all data stores.
ListKeysAsync() Lists all keys in a data store.

AllScopes Property

DataStoreOptions contains an AllScopes property that allows you to return keys from all scopes in a convenient list. You can then use a list item's KeyName property for common data store operations like GetAsync() and RemoveAsync(). When you use this property, the second parameter of GetDataStore() must be an empty string ("").


1local DataStoreService = game:GetService("DataStoreService")
2
3local options = Instance.new("DataStoreOptions")
4options.AllScopes = true
5
6local ds = DataStoreService:GetDataStore("DS1", "", options)
7

If you enable the AllScopes property and create a new key in the data store, you must always specify a scope for that key in the format of scope/keyname, otherwise, the APIs throws an error. For example, gold/player_34545 is acceptable with gold as the scope, but player_34545 leads to an error.

Consider the following data set:

global/K1 house/K1
global/L2 house/L2
global/M3 house/M3

Error Codes

Requests to data stores may occasionally fail due to poor connectivity or other issues. Wrapping data store functions in pcall() can handle any errors and return a message with an error code.

Error Code Reference

Error Code Error Message Notes
101 Key name can't be empty. Check if the key input into the data store function is an empty string.
102 Key name exceeds the 50 character limit. Check if the key input into the data store function exceeds a length of 50.
103 X is not allowed in DataStore. An invalid value of type X was returned by a bad update function.
104 Cannot store X in DataStore. A value of type X returned by the update function did not serialize.
105 Serialized value converted byte size exceeds max size 64*1024 bytes. Character count in string cannot exceed 65,536 characters.
106 MaxValue and MinValue must be integers. If you're passing a minimum or maximum value to GetSortedAsync() for an OrderedDataStore, both values must be integers.
106 PageSize must be within a predefined range. The maximum page size for an OrderedDataStore is 100.
301 GetAsync request dropped. Request was throttled, but throttled request queue was full. GetAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
302 SetAsync request dropped. Request was throttled, but throttled request queue was full. SetAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
303 IncrementAsync request dropped. Request was throttled, but throttled request queue was full. IncrementAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
304 UpdateAsync request dropped. Request was throttled, but throttled request queue was full. UpdateAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
305 GetSorted request dropped. Request was throttled, but throttled request queue was full. GetSortedAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
306 RemoveAsync request dropped. Request was throttled, but throttled request queue was full. RemoveAsync() request has exceeded the maximum queue size and Roblox is unable to process the requests at the current throughput.
401 Request Failed. DataModel Inaccessible when the game is shutting down. DataModel is uninitialized because the game is shutting down.
402 Request Failed. LuaWebService Inaccessible when the game is shutting down. LuaWebService is uninitialized because the game is shutting down.
403 Cannot write to DataStore from Studio if API access is not enabled. API access must be active in order to use data stores in Studio.
404 OrderedDataStore does not exists. The OrderedDataStore associated with this request has been removed.
501 Can't parse response, data may be corrupted. System is unable to parse value from response. Data may be corrupted.
502 API Services rejected request with error: X. Error X occurred when processing on Roblox servers. Depending on the response, you may want to retry the request at a later time.
503 DataStore Request successful, but key not found. The key requested was not found in the data store. Ensure the data for the key is set first, then try again.
504 Datastore Request successful, but the response was not formatted correctly. Data retrieved from GlobalDataStore was malformed. Data may be corrupted.
505 OrderedDatastore Request successful, but the response was not formatted correctly. Data retrieved from OrderedDataStore was malformed. Data may be corrupted.
511 Metadata attribute size exceeds 300 bytes limit. The serialized metadata size exceeds the limit. The value 300 is dynamic, if the size changes, the value also changes.
512 UserID size exceeds limit of 4. The caller provided too many user IDs in the user IDs array.
513 Attribute userId format is invalid. The user ID provided is not a number.
513 Attribute metadata format is invalid. The metadata is not a table.

Limits

There are also limits applied to the data store model. If an experience exceeds these limits, the service automatically throttles the experience's data store usage, causing requests to be placed in a queue.

When a limit is reached, further requests are placed into one of four queues: set, ordered set, get, and ordered get. Requests in a queue are handled in the order they are received and the called function continues to yield as long as its request is queued. If the data store key itself is throttled, the request is temporarily skipped but still in the queue. Each queue has a limit of 30 requests and, when this limit is exceeded, the requests fail with an error code in the 301–306 range indicating that the request was dropped entirely.

Server Limits

Each server is allowed a certain number of data store requests based on the request type and number of players (more data is needed for more players).

Request Type Functions Requests per Minute
Get GetAsync() 60 + numPlayers × 10
Set (limit is shared among all listed functions) SetAsync()
IncrementAsync()
UpdateAsync()
RemoveAsync()
60 + numPlayers × 10
Get Sorted GetSortedAsync() 5 + numPlayers × 2
Get Version GetVersionAsync() 5 + numPlayers × 2
List ListDataStoresAsync()
ListKeysAsync()
ListVersionAsync()
5 + numPlayers × 2
Remove RemoveVersionAsync() 5 + numPlayers × 2

Experience Limits

The following functions have experience-wide limits that apply regardless of which server calls them.

Request Type Functions Cooldown
Set (same key) SetAsync()
IncrementAsync()
UpdateAsync()
RemoveAsync()
6 seconds between write requests.

Data Limits

Along with request frequency, data stores limit how much data can be used per entry. The data store name, key name, and scope must all be under a certain character length, as well as the amount of data stored.

Component Maximum Number of Characters
Data Store Name 50
Key Name 50
Scope 50
Data (Key Value) 4,194,304 per key

Caching

Keys cache locally for 4 seconds after the first read. A GetAsync() call within these 4 seconds returns a value from the cache. Modifications to the key by SetAsync() or UpdateAsync() apply to the cache immediately and restart the 4 second timer.

GetVersionAsync(), ListVersionsAsync(), ListKeysAsync(), and ListDataStoresAsync() don't implement caching and always fetch the latest data from the service.

Best Practices

The following guidelines help you manage your data more efficiently and take advantage of future improvements.

Create Fewer Data Stores

Data stores behave similarly to tables in databases. Minimize the number of data stores in an experience and put related data in each data store. This approach allows you to configure each data store individually (versioning and indexing/querying) to improve the service's efficiency to operate the data. As more features become available, this approach also allows you to more easily manage data stores.

Since the increased maximum object size is 4 MB, there should be enough space to follow this rule. You can fetch all relevant data in one call and use the quota (limit) more efficiently. Additionally, SetAsync() updates all data, so all data for the same player is always in sync. The versioning system versions individual objects rather than the whole data store. When restoring to older versions, self-contained objects are now consistent and useful.

Use Key Prefixes to Organize Your Data

You can filter keys with a certain prefix when calling ListKeysAsync(). Consider an experience that supports players having multiple character profiles. You can save keys with a prefix such as /User_1234/profiles/warrior and /User_1234/profiles/mage and use a prefix search with /User_1234/profiles to get a list of all profiles.