Save your player data with ProfileService! (DataStore Module)


Mad Studio cross-promo:

Check out ReplicaService for state replication / networking!
If you’re curious about how Madwork is written, give MadLife - code_guidebook a read!


Madwork - ProfileService

ProfileService on GitHub

ProfileService is a stand-alone ModuleScript that specialises in loading and auto-saving
DataStore profiles.

It’s documented:
ProfileService wiki

It’s open source:
Roblox library

Watch while you eat pizza on the couch - YouTube tutorials:
ProfileService tutorial playlist by @okeanskiy
Session-locking explained and savable leaderstats by @EncodedLua
(Will add new tutorials as they come)

A DataStore Profile (Later referred to as just Profile) is a set of data which is meant to be loaded up
only once inside a Roblox server and then written to and read from locally on that server
(With no delays associated with talking with the DataStore every time data changes) whilst being
periodically auto-saved and saved immediately once after the server finishes working with the Profile.

The benefits of using ProfileService for your game’s profiles are:

  • This is my personal module that I intensively support - any bugs you find are also my bugs - In the very unlikely (astronomically unlikely :sunglasses:) case you do find a bug I’ll usually fix it in around 2 - 3 days if you can give me a decent report.

  • Easy to learn, and eventually forget - ProfileService does not give you any data getter or setter functions. It gives you the freedom to write your own data interface.

  • Built for massive scalability - low resource footprint, no excessive type checking. Great for 100+ player servers. ProfileService automatically spreads the DataStore API calls evenly within the auto-save loop timeframe.

  • Already does the things you wouldn’t dare script yourself (but should) - session-locking is essential to keeping your data protected from multiple server editing - this is a potential cause of item loss or item duplication loopholes. ProfileService offers a very comprehensive and short API for handling session-locking yourself or just letting ProfileService do it automatically for you.

  • Future-proof - with features like MetaTags and GlobalUpdates, you will always be able to add new functionality to your profiles without headaches.

  • Made for ambitious projects - ProfileService is a profile object abstraction detached from the Player instance - this allows the developer to create profiles for entities other than players, such as: group-owned houses, savable multiplayer game instances, etc.


ProfileService is part of the Madwork framework
Developed by loleris

Example code:

-- ProfileTemplate table is what empty profiles will default to.
-- Updating the template will not include missing template values
--   in existing player profiles!
local ProfileTemplate = {
    Cash = 0,
    Items = {},
    LogInTimes = 0,
}

----- Loaded Modules -----

local ProfileService = require(game.ServerScriptService.ProfileService)

----- Private Variables -----

local Players = game:GetService("Players")

local GameProfileStore = ProfileService.GetProfileStore(
    "PlayerData",
    ProfileTemplate
)

local Profiles = {} -- [player] = profile

----- Private Functions -----

local function GiveCash(profile, amount)
    -- If "Cash" was not defined in the ProfileTemplate at game launch,
    --   you will have to perform the following:
    if profile.Data.Cash == nil then
        profile.Data.Cash = 0
    end
    -- Increment the "Cash" value:
    profile.Data.Cash = profile.Data.Cash + amount
end

local function DoSomethingWithALoadedProfile(player, profile)
    profile.Data.LogInTimes = profile.Data.LogInTimes + 1
    print(player.Name .. " has logged in " .. tostring(profile.Data.LogInTimes)
        .. " time" .. ((profile.Data.LogInTimes > 1) and "s" or ""))
    GiveCash(profile, 100)
    print(player.Name .. " owns " .. tostring(profile.Data.Cash) .. " now!")
end

local function PlayerAdded(player)
    local profile = GameProfileStore:LoadProfileAsync(
        "Player_" .. player.UserId,
        "ForceLoad"
    )
    if profile ~= nil then
        profile:Reconcile() -- Fill in missing variables from ProfileTemplate (optional)
        profile:ListenToRelease(function()
            Profiles[player] = nil
            -- The profile could've been loaded on another Roblox server:
            player:Kick()
        end)
        if player:IsDescendantOf(Players) == true then
            Profiles[player] = profile
            -- A profile has been successfully loaded:
            DoSomethingWithALoadedProfile(player, profile)
        else
            -- Player left before the profile loaded:
            profile:Release()
        end
    else
        -- The profile couldn't be loaded possibly due to other
        --   Roblox servers trying to load this profile at the same time:
        player:Kick() 
    end
end

----- Initialize -----

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

----- Connections -----

Players.PlayerAdded:Connect(PlayerAdded)

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

ProfileService is server-side only - you’ll need replication code to pass Profile.Data to clients. ReplicaService was designed for this job!

597 Likes
Making Secure and Safe Datastores
Preventing item duplication via trading
Best way to store player properties?
How Reliable are Data Stores?
Data Saving - DataStoreService
Stop using SetAsync() to save player data
[ProfileService] Not properly banning user
Data not being saved
Is UpdateAsync() actually better than SetAsync()?
Multiple Datastores needed?
How would I go about making a fully saving inventory
Help with Inventory System Concept
So You Wanna Script: A Reference Guide for Everyone Else
Open Sourced Inventory System
How to make DataStores?
Duping On Cross Platform Games
Data Store Issues
Update data without overriding data in other servers
How to make a Strong DataStore
Are there any improvements I should make to this data store
Fixing a shop saving problem
DataStore2 Stuck?
How to use DataStore2 - Data Store caching and data loss prevention
Error: attempt to perform arithmetic (sub) on number and nil on ProfileService DataStore
InstanceDataStore-V2 (PRE-UPDATE) | Save any instance easily
Profile Service connect to gui
How Can I Improve This Pet Saving System?
How to use DataStore2 - Data Store caching and data loss prevention
Update Log - Build a Private Island (Jan 27)
Help with datastores
Money wont save
How would I go about making docs for modules?
Roblox Trading System
Is it worth switching to datastore 2?
How do I use Data stores?
How can i make a datastore for this?
How to Script Some Game Essentials
Why is this script not saving data?
Learn to Script / Learn to Code in 5 Steps
How to properly save data with dev products and when
How to make equivalent of Instance:GetFullName for tables
Exploiting Datastorage: REMOTE FUNCTIONS?
DataStore2 data loss on disconnect
ProfileService cross-game
How to properly utilize UpdateAsync
How do i make this script more efficient?
Help with Ban table
Help with Ban table
Is there a way to handle the DataStore corruption error?
Dataloss issue/datastore2 advice needed!
Profile Service Data Store
What should I use? DataStore or DataStore2
PlanetQuest Credits
QuickNetwork - A powerful hybrid alternative to ProfileService and DataStore2
How should I save my table?
DataStore Not working
CEngine, a nice framework
Which datastore method should I be using?
I'm having data loss
Issue loading player data with ProfileService
EZGamepass - Developer Products and Gamepasses made easy and secure respectively :O
Feedback on my datastore module
Strange issue prevents data from saving in all places unless someone else joins Team Create
Tips on saving large amounts of data
Datastore not saving
DataStore isn't saving Players points (FIXED)
Setup Rojo Fast: The easy way to setup Rojo with Git support
How should I work within the 6-second DataStore write limit?
Using DataStore2 for multiple saves
DS2Handler - An easier, simpler way to use DS2 (Open Source)
How does ProfileService have session locking?
PlayerAdded Not firing?!
List of tools and tutorials to maximize the productivity of your project
Superblox Racing - Information & Credits
LocalScript FireServer not firing when player leaves the game

This sounds extremely useful, as I find it difficult sometimes to create reliable datastores/properly handle saving and loading player data.

I assume from reading the post that this module handles errors when loading/saving data?

20 Likes

It silently handles all errors while maintaining expected behavior of all its methods and it sends the errors to the endpoints (like ProfileService.IssueSignal) if you wish to set up analytics. Errors will also appear as warnings in the developer log by default.

24 Likes

Really like how efficient this module is. The Global Update element is extremely useful. Especially given it has no additional expense as far a date store calls go. The ease of use and error handling further boost the Mad greatness (pun intended) of the Profile Service. Will definitely be making use of it in a future project.

9 Likes

Came in clutch with this module loleris, I was just looking for a datastore module to use for my game. Thanks.

5 Likes

This is so nicely written and absolutely useful! Legend!

5 Likes

This is a really efficient module, I don’t think people realise that abusing datastores doesn’t make a better datastore. They kind of go with the “if it works it works” attitudes of things.

Thanks for making every compulsive perfectionist’s dream come true.

8 Likes

This is definitely great for newer developers!

However, would there be an advantage to using this over a standard data-storage handler?

In addition, does this provide competition to DataStore2, which is already used by many?

7 Likes

On the contrary, it’s a module well suited for beginners while pretty powerful for advanced scripters - ProfileService is disconnected from Player join / leave events and offers no :Get() or :Set() methods for the profile data - all of this is left to be managed by the developer according to their personal preferences. ProfileService API is short and to the point - it does nothing but handle profiles and it does it perfectly.

Also this module is based on the DataStore implementation I’ve been using in The Mad Murderer 2 where data loss AND item duping were never reported except the time when Roblox did a big oopsie.

15 Likes

Awesome! I’ll have to try it soon for newer projects, I’ll have to come back with results soon :slight_smile:

3 Likes

Amazing job on this! The use of a single DataStore API endpoint had me baffled for a minute, but after looking through it I found your solution to session locking ingenious. This seems like a completely superior data management solution to any others that I’ve seen and I have no doubt in its ability to gain traction.

9 Likes

Will ProfileService automatically release if the server crashes without calling BindToClose? You could possibly abuse MessagingService to check if a server is still alive.

3 Likes

If the server crashes the Profile will indeed become session locked by a dead session… Actually I decided to opt-out not_released_handler = “StealSession” functionality which would let the developer instantly steal the session lock for a profile, because it would not be a 100% safe way to handle data. Basically, the dead session locked profile problem solves itself when the developer is using ProfileService as they regularly would:

After passing not_released_handler = “ForceLoad” (or return "ForceLoad" via function) the player would be forced to wait for the game to load for little over a minute. ProfileStore would fail to make the dead session release the profile and would “steal” the session lock after a built-in timeout (which is, again, about 80’ish seconds). Try to make the player enjoy the passage of time via an animation that would simulate progress, some sort of entertaining animation or even a small minigame.

If, however, your game has profiles that may persist after a player leaves, then you would first try to teleport the player to the JobId returned by the not_released_handler (“Cancel” the profile load request to finish the ProfileStore:LoadProfileAsync() call and use TeleportToPlaceInstance. If the teleport fails, call ProfileStore:LoadProfileAsync() again with “ForceLoad” on this session. Obviously in such system you would need to check if a profile is loaded inside the server for that player, because calling ProfileStore:LoadProfileAsync() again before releasing the same profile will cause an error.

If you wish to create your own custom logic of asking a LIVE remote session to release the profile, then the return "Repeat" callback for not_released_handler can be handy - Passing “Repeat” waits for 15 seconds and retries session locking the profile. If the profile has not been released, not_released_handler is called again.

8 Likes

This is awesome @loleris, thanks for sharing us your resources

- can you add a source file for using with rojo on Github? nvm

  • does it use MessageService internally as well?
4 Likes

ProfileService can be applied within your Rojo workflow as any regular ModuleScript you would create for your game yourself.

ProfileService only relies on :UpdateAsync() to power all of its features - it will not know whether the Roblox server that has the profile currently locked is actually alive or dead. However, in practice we don’t need to know that as most Roblox servers will not be crashing everyday and the only downside to Roblox servers crashing one day is that your profile requests will be handled in a minute instead of 1-2 seconds.

3 Likes

.OnUpdate doesn’t work how does this module address that?

  • so I was wondering how the code detects changes across servers?

I know that the code is compatible, I meant set up like this for Rojo https://github.com/Sleitnick/AeroGameFramework/tree/master see src Folder nvm I thought it wasn’t a standalone module like most, sorry my bad

I haven’t got to read the source yet

3 Likes

Instead of subscribing to changes it scans for changes periodically every auto-save which happens every 30 seconds for every active Profile.

3 Likes

A wiki page with a completely barebones “getting started” example could be very useful for beginner scripters - one of the reasons some prefer DS2. For example, omitting the player:IsDescendantOf if statement for the sake of simplicity. Also I might have misread the example, but there doesn’t seem to be a use for Workspace.

A quick example using how global updates might interest people too.

8 Likes

I might use this as a secondary backup

4 Likes

In my opinion, this module should be used as your primary data management solution with bereza’s method of saving data as backups, if you really want them. This module seems to handle lots of nasty edge cases you ordinarily would just have to encounter yourself before you figure out how painstakingly difficult they are to fix.

2 Likes