[PlayerDataStore] Roblox's Most Advanced Player Datastore Yet Easiest To Use

PlayerDataStore

Roblox’s Most Advanced Player Datastore Yet Easiest To Use

Roblox has made Database way more complicated than it has to be, not only that but there’s also the possibility of data loss, data corruption, hitting limits, limitations, and a lot of more issues. That’s when modules such as ProfileStore for Datastore gets used to prevent those data issues, however, it still doesn’t address a few issues that the vast majority of Roblox developers struggle with which is that it isn’t easy to use and very complicated. Not only that, it still uses an outdated system ‘DataStore’ for session-locking which as a result, reduces your DataStore limits and is also extremely slow. Along with all of that, ProfileStore and most of other modules don’t support Roblox datatypes such as Vector3, Vector2, Color3, CFrame, etc at least not without specifying their types manually.

PlayerDataStore addresses and fixes all of these issues for you.

Contact

.ranu.

Some of the Games that Use it


Overview

What is PlayerDataStore?

PlayerDataStore is Roblox’s easiest to use yet most powerful player-oriented Roblox DataStore System that has all the modern systems to prevent all the possible issues.

It comes with session-locking using MemoryStore, and is the only DataStore System in Roblox with MemoryStore that is prepared for extremely rare scenarios where Roblox MemoryStore fails (which has only happened a couple times in the past, as of today). Just a couple days ago, it took down thousands of games that were using MemoryStore. With this module, that will never happen to you even if MemoryStore fails. It also has auto-retry system in case DataStoreService fails.

It also comes with Default Player Data Template, and has auto-reconciliation.

It is more performant and easier to use than the most popular DataStore system in Roblox ProfileStore

It leaves no chance for Data loss and Data corruption, while it also provides all the features that you’d realistically need in a Datastore System.

It supports reading/updating data to both Offline and Online players, meaning whether they are in your game server or not, it will be able to work smoothly even if they are in different servers of your game.

It also uses caching system, and makes the minimum use of MemoryStore & DataStore so your game servers & game limits wouldn’t be reached/throttle. Way less than ProfileService.
The module makes proper and minimum use of DataStore & MemoryStore & MessageService.

It is one of the very few DataStore systems in Roblox that support most of the Roblox datatypes such as CFrame, Ray, Vector3, Vector2, Vector3int16, Vector2int16, Color3, NumberSequence, and a lot more without you even having to specify their types.

It comes with a serializer and also comes without the JSON overhead at the fastest speed possible.
It is also highly configurable, has autosave, has configs to enable/disable serializer, separating Studio/Client datastore/session-lock, feature to pretend to be a new user playing your game for first time, and a lot more.

Most of its methods accept player instance, player userid as number, string because we know that a lot of developers suffer from giving the wrong types of datatypes, for example instead of giving userid as a number, they give it as a string. This handles all of that for you smoothly.

Most methods yield such as :SetValue, :GetValue, :GetData, etc if they are in the server, with a timeout automatically, so you don’t even have to worry about their data initializing at first when they join.

The module comes with auto-correct for the default player data template.
It has Erase User data method and is GDPR compliant.

It also has a bit of tutorial and full documentation in it.

Also, Automatically checks for updates.

API

	Functions:
		
	1. GetData(player: Player): table?	[comes with Reference]
	   - Returns the full data table for the player.
	   - Returns nil if data is not loaded.
	
	2. GetValue(player: Player, ...: string): (any, boolean)	[can come with Reference]
	   - Returns the value for a specific key in the player's data.
	   - Returns nil if data not loaded or key doesn't exist.
	   - Second return value is false if the player's data wasn't loaded, otherwise true.
	   
	   # Recommended to Use Instead #
	   GetOfflineValue = FetchValue
	2.5. FetchValue(player: Player | number | string, ...: string): any	[can come with Reference]
	   - Retrieves a nested value from a player's offline data.
	   - If they are online in the server, then it will use their current cached data from the server.
	   - Accepts a Player instance, userId (number), or userId (string).
	   - If the player is currently online, falls back to GetValue.
	   - Returns nil if data is missing or the key path does not exist.
	   
	3. SetValue(player: Player | number | string, ...: any)
		- Sets a nested value inside the player's session data or offline data using a variable number of keys.
		- The first argument can be a Player instance, a userId number, or a userId string.
		- The following arguments are one or more keys followed by the final value to set.
		- If the player is online, waits for their data to load; if data is not loaded, returns early without setting.
		- If the player is offline, loads their offline data, applies the nested set, saves the data, and publishes a message for synchronization.
		- Returns true on success; otherwise returns nil.

	4. UpdateData(player: Player | number | string, changes: table)
		- Applies multiple updates to the player's session data or offline data in one call.
		- The first argument can be a Player instance, a userId number, or a userId string.
		- The second argument is a table of key/value pairs representing updates.
		- Nested tables are supported, and will automatically create intermediate tables if they do not exist.
		- If the player is online, waits for their data to load and applies all changes in memory.
		- If the player is offline, loads their offline data, applies all changes, saves the data once,
		  and publishes a "BulkUpdate" message for synchronization.
		- Returns true if all updates were successfully applied; otherwise returns false.
	
	5. Increment(player: Player | number | string, ...: any) -- returns true if succeeded
	   - Increments a nested numeric key by the specified amount.
	   - Initializes missing nested structure and key to 0 if needed.
	   - Supports both online and offline data (if OfflineModeEnabled).
	   - Does nothing if data is not loaded or unavailable.
	   - Returns true on success; otherwise returns nil.
	
	6. HasData(player: Player): boolean
	   - Returns true if player data is loaded in session cache, else false.
	
	7. RemoveKey(player: Player | number | string, ...: any) -- returns true if succeeded
	   - Removes a specific nested key from the player's data.
	   - Works for both online and offline players:
	     • If the player is online and session data is loaded, it modifies sessionData.
	     • If the player is offline, it loads and updates the offline data file.
	   - Does nothing if data is not loaded or path is invalid.
	   - Returns true on success; otherwise returns nil.
	   
  	8. * GDPR Compliance - Right to Erase User Data:
		:EraseUserData(userId: number | string)
		
		
	>> Advanced From Here <<
	
	* (Advanced) WaitForData(player: Player, timeout: number?): boolean
	    - Waits for player data to be loaded or timeout (default 15s).
	    - Returns true if data is loaded and player still in game; false otherwise.

	* (Advanced) :Reconcile(player: Player | number | string, data)	* (Advanced) :Reconcile(player: Player | number | string, data) : boolean
	* (Advanced) :GetDefaultTemplate()
	* (Advanced) :_DontSaveOnLeaveForPlayer(player: Player | number | string) : boolean	    

	* (Advanced) Specific Offline Datastore Functions:
		:LoadOfflineData(player: Player | number | string)	[can come with Reference]
		:SaveOfflineData(player: Player | number | string, data: any)
		
		(Online / Offline)
		:FetchValue(player: Player | number | string, ...: string): any	[can come with Reference]

Usage

It’s the easiest yet the most powerful DataStore module in all of Roblox, just look at how easy it is to use.

Here’s a couple videos of its usage:

Offline Data Modification Example

local OfflineLoadedData = PlayerDataStore:LoadOfflineData(3267747943)
print(OfflineLoadedData)
OfflineLoadedData.Hi = {}
OfflineLoadedData.Col = Color3.fromRGB(0, 200, 50)
OfflineLoadedData.Hi3 = false
OfflineLoadedData.Hi4 = 6
OfflineLoadedData.Pos = Vector3.new(1,5,2)
PlayerDataStore:SaveOfflineData(3267747943, OfflineLoadedData)

or even an easier and more efficient way you can just do like this and it will work like magic whether they are online, offline, or in another server of the game:

UpdateData(player, { -- or UpdateData(3267747943, {
    Stats      = { Kills = 10, Deaths = 3 },
    TestStats  = { f = 1, t = { b = { n = 1 } } },
    Test       = true,
    N          = 2,
    Text       = "test"
})

As you can see above, it also supports Write to data without necessarily having to use :SetValue, but instead by getting a reference to the cached data and making changes to it which would directly update the cached data.
This can also be done in both online and offline mode, in online mode using GetData or GetValue/GetOfflineValue you can get reference to the cached data.
You also don’t have to worry whether their data was initialized before or not, it safely handles everything for you.

The most special thing about this module is that everything is handled for you, you don’t have to worry about anything. You don’t need to worry about Profiles, initializing Session-locking, clearing cached data, roblox datatypes, specifying datatypes, data loss/corruption, and all other stuff. You just straight get to use it!

Comparison between Usage of ProfileStore and PlayerDataStore

In this comparison, both modules will be achieving the same goal.
ProfileStore Usage Example:

local ProfileStore = require(game.ServerScriptService.ProfileStore)

-- The PROFILE_TEMPLATE table is what new profile "Profile.Data" will default to:
local PROFILE_TEMPLATE = {
   Cash = 0,
   Items = {},
}

local Players = game:GetService("Players")

local PlayerStore = ProfileStore.New("PlayerStore", PROFILE_TEMPLATE)
local Profiles: {[Player]: typeof(PlayerStore:StartSessionAsync())} = {}

local function PlayerAdded(player)

   -- Start a profile session for this player's data:

   local profile = PlayerStore:StartSessionAsync(`{player.UserId}`, {
      Cancel = function()
         return player.Parent ~= Players
      end,
   })

   -- Handling new profile session or failure to start it:

   if profile ~= nil then

      profile:AddUserId(player.UserId) -- GDPR compliance
      profile:Reconcile() -- Fill in missing variables from PROFILE_TEMPLATE (optional)

      profile.OnSessionEnd:Connect(function()
         Profiles[player] = nil
         player:Kick(`Profile session end - Please rejoin`)
      end)

      if player.Parent == Players then
         Profiles[player] = profile
         print(`Profile loaded for {player.DisplayName}!`)
         -- EXAMPLE: Grant the player 100 coins for joining:
         profile.Data.Cash += 100
         -- You should set "Cash" in PROFILE_TEMPLATE and use "Profile:Reconcile()",
         -- otherwise you'll have to check whether "Data.Cash" is not nil
      else
         -- The player has left before the profile session started
         profile:EndSession()
      end

   else
      -- This condition should only happen when the Roblox server is shutting down
      player:Kick(`Profile load fail - Please rejoin`)
   end

end

-- In case Players have joined the server earlier than this script ran:
for _, player in Players:GetPlayers() do
   task.spawn(PlayerAdded, player)
end

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
   local profile = Profiles[player]
   if profile ~= nil then
      profile:EndSession()
   end
end)

VS PlayerDataStore:

local PlayerDataStore = require(game.ServerScriptService.PlayerDataStore)
local Players = game:GetService("Players")

--[[ You can use this along with Reconcile, if you don't wanna put it into DefaultData Template Module.
local PROFILE_TEMPLATE = {
	Cash = 0,
	Items = {},
}
--]]

local function PlayerAdded(player)
	-- EXAMPLE: Grant the player 100 coins for joining:

	-- PlayerDataStore:Reconcile(player, PROFILE_TEMPLATE)
	
	-- METHOD #1:
	PlayerDataStore:Increment(player, "Cash", 100)

	--[[
		METHOD #2 of doing it:
		
		local PlayerData = PlayerDataStore:GetData(player) -- yields
		
		if PlayerData then
			PlayerData.Cash += 100
		end
	]]
end

-- In case Players have joined the server earlier than this script ran:
for _, player in Players:GetPlayers() do
	task.spawn(PlayerAdded, player)
end

Players.PlayerAdded:Connect(PlayerAdded)

PlayerDataStore also supports most of Roblox datatypes such as Vector3, CFrame, Color3 which most of other modules including ProfileStore don’t, and those that do require you to specify their types. With this module, you have to worry about none of that.

Pricing

The module is currently in sale for 10,000 Robux (tax-covered), can be both USD and Robux. (Preferably Robux)
you can also get it for free if you are an organization / renowned studio, if you credit it.

Other Projects: Ro-Photoshop [Fastest Image Editor & Drawing Module] (Parallel)

2 Likes

Where is the api to properly handle Developer products?
What happens if DatastoreService is down? How can users properly disable Developer Products or warn the user that their data has failed to load?

I’m not sure what you mean with Developer products, this is a Datastore System.

What happens if DatastoreService is down?

It has retry logic in place, meaning it will try a few more times to save the data if it fails.

Exactly, when buying Developer Products it’s important that you property save the changes before returning Enum.ProductPurchaseDecision.PurchaseGranted.

What if the user buys 1000 gold then leaves and the datastore fails to save. That user has just lost the 1000 gold they just bought. So you’re basically scamming them.
Properly handling developer products is an important part of a Datastore System.

This is not an answer to my question. What if the retries fail? At what point will it stop retrying? How can the user tune into your datastore system and properly know when the data has failed to load and react to it like telling the player their data failed to load or kick them?

By the looks of it the only reason your datastore system is the ‘Easiest To Use’ is because it lacks a lot of core features a good datastore system like ProfileStore or Suphi’s datastore has.

What if the user buys 1000 gold then leaves and the datastore fails to save. That user has just lost the 1000 gold they just bought. So you’re basically scamming them.
Properly handling developer products is an important part of a Datastore System.

This is handled, In Gifting Currency with Dev Product Prompt for example, you just use GetOfflineValue to get their current currency, and (:SetValue their currency + their older currency) or just :Increment (the second one is recommended in this case). It works if they are online, and Even if they’ve left the game, it will still work. EVEN if they’re in another server of the game by updating the data in the other server aswell. It works like a magic which no other module can. This would be 10 times more complicated in any other module.

This is not an answer to my question. What if the retries fail? At what point will it stop retrying? How can the user tune into your datastore system and properly know when the data has failed to load and react to it like telling the player their data failed to load or kick them?

There is a 3 retry limit for saving data, if all the 3 fails then it gives up. There is nothing else we can do, if Roblox DataStore has failed. We could resort to MemoryStore for temporarily saving their data, but that’d be too much considering Roblox Datastore is almost never running into issues anymore unlike the past. Roblox also has added auto-retry logic at backend of their datastore, and they’ve made their datastore much more stable than ever before. Making the need of retry almost 0. Even though, we still have a retry system to tell people that we have it otherwise even Roblox confirmed that there’s no longer need for that.

If data has failed to load, then it kicks them, that’s the only thing you can do in that case without causing data corruption, or data loss.

By the looks of it the only reason your datastore system is the ‘Easiest To Use’ is because it lacks a lot of core features a good datastore system like ProfileStore or Suphi’s datastore has.

It is hardly missing anything, it is player-oriented and has everything that you’d realistically need, it even has way more things that you’d realistically need as compared to ProfieStore.

what’s the purpose of having seperate methods for loading/saving offline data? since your datastore key should be a string(or numbers, but those are converted to strings anyway), so you should be able to just use the same loading/saving methods for offline as you do for online

also do you have any benchmarks to show the performance of the module(specifically compared to other major datastore wrappers such as ProfileService or DataStoreV2)?

lastly

do you perchance have a citation for this statement?
as far as I’ve found, there’s been no such confirmation made

So :SetValue, by itself loads data, sets data, saves data if they are offline. That’s 1 GetAsync, and 1 SetAsync per call. In those instances, where you’d need to mass change values in it, you’d LoadOfflineData, changes the values in it, and then SaveOfflineData. Otherwise, SetValue would be a lot of calls that you wouldn’t want. There’s the example for it above.

also do you have any benchmarks to show the performance of the module(specifically compared to other major datastore wrappers such as ProfileService or DataStoreV2)?

I don’t, however, I know how they work and I know how this works based on that I can tell you this is faster, also this uses a BufferEncoder (enabled as default, can also be disabled). which beats the performance JSON Encode/Decode in most cases. For the session-locking, ProfileStore also uses Datastore, and we use MemoryStore which has lower latency and much faster.

do you perchance have a citation for this statement?
as far as I’ve found, there’s been no such confirmation made

Check Roblox Plans for 2024, they mentioned in one of their planned updates, that they were going to introduce their own retry system into the datastore, so there would be no more need for that. They also mentioned a few other things. Not only that, they’ve promised to even implement their own session-locking in 2026 into datastore in another planned-updates thread this year. I’m not going to bother searching through whole devforum, however, you can find them if you search thoroughly.

1 Like

Do you have benchmarks for this? I’m interested in seeing how it performs given that it automatically compresses data down. Speaking of, how does the compression work? I also don’t see any mention of different scopes, something ProfileStore has and is quite useful at scale

also this might just be me but i think it’s a little deceptive to not explicitly state that the linked roblox game is a project you personally worked on, not trying to dismiss the module, just saying lol

I don’t really have any benchmark at the moment, although, I explained some of the reasons why this is usually faster than other modules in the reply above.

The buffer encoder depending on the structure of the data can usually be faster than JSONEncode/JSONDecode. It doesn’t have major compression system, however, as compared to JSON it has way less size, and for some datatypes, the data is minimized leading to smaller size.

also this might just be me but i think it’s a little deceptive to not explicitly state that the linked roblox game is a project you personally worked on, not trying to dismiss the module, just saying lol

I don’t think that matters, and I didn’t think of it that way lol.

EDIT: I forgot to reply to the scopes parts, no currently, it doesn’t support multiple. As the name of the module ‘PlayerDataStore’, it is fully dedicated to player datastore and is player-oriented. 99% of Roblox Games only need that and nothing else.
Although, in future updates, new things could be added.

This module is also safe to be combined with other modules/systems/datastore modules. Meaning, if you have things unrelated to players, you could use ProfileService if that’s better for you and use this for player ordinary data, since this one is fully player-oriented.

You brought up that ProfileStore uses an apparently outdated method for session locking which takes up some unnecessary rate limit budget, but there are no metrics to compare them here

The whole point of scopes is to logically organize your data, so if I need player data I can look in the player data store, and if I need something separate from that, I can create another scope. I also don’t think it’s a good idea to share the data store limits between two uncoordinated modules

I think it’s pretty neat but I’m not sold on the R$10,000 price tag. If the idea was for bigger projects with some funding to use this, ProfileStore is free, reliable, and not difficult to set up at all! There is a very good video tutorial on how to get over the initial hurdle of setting it up, but after that, it’s as easy as setting a value in the profile.Data table

With a session locking datastore system, offline data is more tricky, because another server could have said session open, preventing you from modifying it. In such case, the server owning the session lock would have to be contacted through with MessagingService, to then handle the changes to the data. If the session is not locked, then updating offline data is less of an issue. Still need to lock the session though

Note that session locking is not a necessity for keeping data safe, although it is by far the most common system

Such systems can be faster than session locking if they use a single UpdateAsync call, you do have to organize updates in such a way that the order of updates doesn’t matter, to avoid data loss

It uses MessageService aswell. Also, I don’t agree with the last argument.

It’s far simpler than ProfileStore, and easier to use. There’s way too many complaints on how difficult it is to use ProfileStore. Just because you are familiar with it, doesn’t mean others are.

It’s a long read, but you might find it interesting. It showcases how data is kept safe while abusing it quite badly. This approach is more complicated in the way data is updated, but it allows for multiple servers modifying the same key, and doesn’t need to kick players if data fails to load

I’m actually not that familiar with ProfileStore’s (or even its predecessor, ProfileService’s) API, I literally go to the devforum post and copy paste the sample set up code snippet into each new project that I want to save data; your main selling point here seems to be simplicity, and R$10,000 is an outrageous sum of money to be forking over for that

1 Like

There’s way so many more things than just ‘simplicity’ that this module has, and others don’t. One of them is the Roblox datatypes support, also with no need to specify them manually.

Update Log (v0.3 → 0.4.0):

  • New Update Method:
    function DataStoreModule:UpdateData(player: Player | number | string, changes: {[any]: any}) : boolean
    With one call, you can bulk update the player data and it works in both Online and Offline modes.
    The main reason this was added was so that it could reduce the calls of :SetValue for offline operations. You also no longer have to manually :LoadOfflineData, :SaveOfflineData. This method handles everything for you with minimum calls. It only updates the keys that are in the given table.
    Example:
UpdateData(player, {
    Stats      = { Kills = 10, Deaths = 3 },
    TestStats  = { f = 1, t = { b = { n = 1 } } },
    Test       = true,
    N          = 2,
    Text       = "test"
})
  • New Reconcile method that users can use later if they wanted to:
    function DataStoreModule:Reconcile(player: Player | number | string, data) : boolean

  • New method: DataStoreModule:GetDefaultTemplate(): typeof(Template)

  • New method DataStoreModule:_DontSaveOnLeaveForPlayer(player: Player | number | string) : boolean

  • In Studio, any initial issues with DataStoreService are now logged as warnings (e.g., if API access is not enabled).

  • In Studio, the module prints a warning if the configuration is missing, incorrect, or has been changed depending on the configuration.
  • Added Autocomplete for the default template, as can be seen here:


  • Renamed GetOfflineValue method to FetchValue, however, due to backward compatibility, GetOfflineValue will also still work.

  • Various Improvements