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 Open Cloud APIs for data stores.

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:


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")

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:

KeyScope
houses/User_1234houses
pets/User_1234pets
inventory/User_1234inventory

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:


local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)

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


local DataStoreService = game:GetService("DataStoreService")
local inventoryStore = DataStoreService:GetDataStore("PlayerInventory", "gold")
local success, currentGold = pcall(function()
return inventoryStore:GetAsync("User_1234")
end)

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.

KeyValue
3125060850
35167597920
50530609278000
Player Data
KeyValue
ActiveSpecialEventSummerParty2
ActivePromoCodeBONUS123
CanAccessPartyPlacetrue
Promo Data

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


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
local success, errorMessage = pcall(function()
experienceStore:SetAsync("User_1234", 50)
end)
if not success then
print(errorMessage)
end

Reading Data

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


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
local success, currentExperience = pcall(function()
return experienceStore:GetAsync("User_1234")
end)
if success then
print(currentExperience)
end

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.


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
local success, newExperience = pcall(function()
return experienceStore:IncrementAsync("Player_1234", 1)
end)
if success then
print(newExperience)
end

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.


local DataStoreService = game:GetService("DataStoreService")
local nicknameStore = DataStoreService:GetDataStore("Nicknames")
local function makeNameUpper(currentName)
local nameUpper = string.upper(currentName)
return nameUpper
end
local success, updatedName = pcall(function()
return nicknameStore:UpdateAsync("User_1234", makeNameUpper)
end)
if success then
print("Uppercase Name:", updatedName)
end

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.


local DataStoreService = game:GetService("DataStoreService")
local nicknameStore = DataStoreService:GetDataStore("Nicknames")
local success, removedValue = pcall(function()
return nicknameStore:RemoveAsync("User_1234")
end)
if success then
print(removedValue)
end

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().


local DataStoreService = game:GetService("DataStoreService")
local characterAgeStore = DataStoreService:GetOrderedDataStore("CharacterAges")

Ordered data stores support the same basic functions as default data stores, plus the unique OrderedDataStore: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.


local DataStoreService = game:GetService("DataStoreService")
local characterAgeStore = DataStoreService:GetOrderedDataStore("CharacterAges")
-- Populate ordered data store
local characters = {
Mars = 19,
Janus = 20,
Diana = 18,
Venus = 25,
Neptune = 62
}
for char, age in characters do
local success, errorMessage = pcall(function()
characterAgeStore:SetAsync(char, age)
end)
if not success then
print(errorMessage)
end
end
-- Sort data by descending order into pages of three entries each
local success, pages = pcall(function()
return characterAgeStore:GetSortedAsync(false, 3)
end)
if success then
while true do
-- Get the current (first) page
local entries = pages:GetCurrentPage()
-- Iterate through all key-value pairs on page
for _, entry in entries do
print(entry.key .. " : " .. tostring(entry.value))
end
-- Check if last page has been reached
if pages.IsFinished then
break
else
print("----------")
-- Advance to next page
pages:AdvanceToNextPageAsync()
end
end
end

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.


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


    local DataStoreService = game:GetService("DataStoreService")
    local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
    local success, currentExperience, keyInfo = pcall(function()
    return experienceStore:GetAsync("User_1234")
    end)
    if success then
    print(currentExperience)
    print(keyInfo.Version)
    print(keyInfo.CreatedTime)
    print(keyInfo.UpdatedTime)
    print(keyInfo:GetUserIds())
    print(keyInfo:GetMetadata())
    end
  • 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.


    local DataStoreService = game:GetService("DataStoreService")
    local nicknameStore = DataStoreService:GetDataStore("Nicknames")
    local function makeNameUpper(currentName, keyInfo)
    local nameUpper = string.upper(currentName)
    local userIDs = keyInfo:GetUserIds()
    local metadata = keyInfo:GetMetadata()
    return nameUpper, userIDs, metadata
    end
    local success, updatedName, keyInfo = pcall(function()
    return nicknameStore:UpdateAsync("User_1234", makeNameUpper)
    end)
    if success then
    print(updatedName)
    print(keyInfo.Version)
    print(keyInfo.CreatedTime)
    print(keyInfo.UpdatedTime)
    print(keyInfo:GetUserIds())
    print(keyInfo:GetMetadata())
    end

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:

FunctionDescription
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.


local DataStoreService = game:GetService("DataStoreService")
local experienceStore = DataStoreService:GetDataStore("PlayerExperience")
local DATA_STORE_KEY = "User_1234"
local maxDate = DateTime.fromUniversalTime(2020, 10, 09, 01, 42)
-- Get the version closest to the given time
local listSuccess, pages = pcall(function()
return experienceStore:ListVersionsAsync(DATA_STORE_KEY, Enum.SortDirection.Descending, nil, maxDate.UnixTimestampMillis)
end)
if listSuccess then
local items = pages:GetCurrentPage()
if table.getn(items) > 0 then
-- Read the closest version
local closestEntry = items[1]
local success, value, info = pcall(function()
return experienceStore:GetVersionAsync(DATA_STORE_KEY, closestEntry.Version)
end)
-- Restore current value by overwriting with the closest version
if success then
local setOptions = Instance.new("DataStoreSetOptions")
setOptions:SetMetadata(info:GetMetadata())
experienceStore:SetAsync(DATA_STORE_KEY, value, nil, setOptions)
end
else
-- No entries found
end
end

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.

FunctionDescription
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 ("").


local DataStoreService = game:GetService("DataStoreService")
local options = Instance.new("DataStoreOptions")
options.AllScopes = true
local ds = DataStoreService:GetDataStore("DS1", "", options)

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 throw 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/K1house/K1
global/L2house/L2
global/M3house/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 CodeError NameError MessageNotes
101KeyNameEmptyKey name can't be empty.Check if the key input into the data store function is an empty string.
102KeyNameLimitKey name exceeds the 50 character limit.Check if the key input into the data store function exceeds a length of 50.
103ValueNotAllowedX is not allowed in DataStore.An invalid value of type X was returned by a bad update function.
104CantStoreValueCannot store X in DataStore.A value of type X returned by the update function did not serialize.
105ValueTooLargeSerialized value exceeds X limit.If you're setting a value with SetAsync() or UpdateAsync(), the serialized length of the value cannot exceed the size X. The serialized length of the data can be checked with JSONEncode().
106MaxValueInvalidMaxValue and MinValue must be integers.If you're passing a maximum value to GetSortedAsync() for an OrderedDataStore, it must be an integer.
106MinValueInvalidMaxValue and MinValue must be integers.If you're passing a minimum value to GetSortedAsync() for an OrderedDataStore, it must be an integer.
106PageSizeGreaterPageSize must be within a predefined range.The minimum page size for an OrderedDataStore is 1.
106PageSizeLesserPageSize must be within a predefined range.The maximum page size for an OrderedDataStore is 100.
301GetAsyncThrottleGetAsync 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.
302SetAsyncThrottleSetAsync 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.
303IncreAsyncThrottleIncrementAsync 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.
304UpdateAsyncThrottleUpdateAsync 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.
304TransformThrottleUpdateAsync 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.
305GetSortedThrottleGetSorted 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.
306RemoveAsyncThrottleRemoveAsync 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.
401DataModelNoAccessRequest Failed. DataModel Inaccessible when the game is shutting down.DataModel is uninitialized because the game is shutting down.
402LuaWebSrvsNoAccessRequest Failed. LuaWebService Inaccessible when the game is shutting down.LuaWebService is uninitialized because the game is shutting down.
403StudioAccessToApisNotAllowedCannot write to DataStore from Studio if API access is not enabled.API access must be active in order to use Data Stores in Studio.
404InternalErrorOrderedDataStore does not exist.The OrderedDataStore associated with this request was not found. This may be a sign of data corruption, so you may want to retry the request at a later time.
501InternalErrorCan't parse response, data may be corrupted.The server was unable to parse the response to your request. This may be a sign of data corruption, so you may want to retry the request at a later time.
502RequestRejectedAPI 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.
503InternalErrorData store request successful, but key not found.The key requested was not found in the DataStore. This may be a sign of data corruption, so you may want to retry the request at a later time.
504InternalErrorData store request successful, but response not formatted correctly.The server was unable to parse the response to your request. This may be a sign of data corruption, so you may want to retry the request at a later time.
505InternalErrorOrderedDataStore request successful, but response not formatted correctly.The server was unable to parse the response to your OrderedDataStore request. This may be a sign of data corruption, so you may want to retry the request at a later time.
511AttributeSizeTooLargeMetadata attribute size exceeds X limit.The serialized metadata size exceeds the limit of X. The value X is dynamic, if the size changes, the value also changes.
512UserIdLimitExceededUserID size exceeds limit of X.The length of the user IDs array provided by the user exceeded the limit of X.
513AttributeFormatErrorAttribute userId format is invalid.The user ID provided is not a number.
513AttributeFormatErrorAttribute 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 TypeFunctionsRequests per Minute
GetGetAsync()60 + numPlayers × 10
Set (limit is shared among all listed functions)SetAsync()
IncrementAsync()
UpdateAsync()
RemoveAsync()
60 + numPlayers × 10
Get SortedGetSortedAsync()5 + numPlayers × 2
Get VersionGetVersionAsync()5 + numPlayers × 2
ListListDataStoresAsync()
ListKeysAsync()
ListVersionAsync()
5 + numPlayers × 2
RemoveRemoveVersionAsync()5 + numPlayers × 2

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.

ComponentMaximum Number of Characters
Data Store Name50
Key Name50
Scope50
Data (Key Value)4,194,304 per key

Throughput Limits

The following table describes per-key throughput limits to ensure optimal performance on Roblox servers. Each limit applies to every single key across all servers in the experience, and it refreshes over time. For example, for each request, Roblox examines the usage of quota associated with the key over the last 60 seconds. If the usage including current request is within the throughput limit, the request is approved. However, of the usage exceeds the limit, the request is denied.

Request TypeLimit
Read25 MB per minute
Write4 MB per minute

Caching

By default, the engine stores values that you retrieve from the backend using GetAsync() in a local cache for 4 seconds, and GetAsync() requests for cached keys return the cached value instead of continuing to the backend. This way, your GetAsync() requests returning a cached value don't count towards your throughput and server limits.

Caching also applies to modifications to keys using SetAsync(), UpdateAsync(), IncrementAsync(), and RemoveAsync(). Additionally, all GetAsync() calls that retrieve a value not being cached from the backend update 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 backend.

Disabling Caching

To opt out of using the cache to retrieve the most up-to-date value from the servers, add the DataStoreGetOptions parameter to your GetAsync() call and set its UseCache property to false to make your request ignore any keys in the cache.

Disabling caching is useful if you have multiple servers writing to a key with high frequency and need to get the latest value from servers. However, it can cause you to consume more of your data stores limits and quotas, since GetAsync() requests bypassing caching always count towards your throughput and server limits.

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.