Save your player data with ProfileService! (DataStore Module)

While this module is frikin amazing I have encountered on one of the issues which sorta gave me anxiety. I really appreciate your work but sometimes the ListenToHopReady is unreliable, most of the times testers have lost data, while I removed it the the data loss vanished completely at the cost of loading into battle place being longer.

You don’t necessarily need it, just call some function you’d wanna connect to a profile update where you’re updating the data.

What is “Profile Update”? I’ve searched the Profile Service API but couldn’t find the connection function for what you said so if you’re kind enough can you tell me what exactly this is?

Is there support for easily setting this up for external data stores ?

It’s not in the API of ProfileService. You have to create your own function that updates the data of your profile and your own signal that you fire in said function.
Example:

function setData(profile, key, value)
    local oldValue = profile.Data[key]
    --Checks if type of newValue is the same as oldValue for safety such as adding currency but you put in a string as an argument when the currency is an integer.
    if typeof(value) ~= typeof(oldValue) then return end

    profile.Data[key] = value
    --Assume that you have a custom signal and fire it
   dataChangedSignal:Fire(whateverYouWant)
end

ProfileService has been a huge help but one thing I’m really used to with datastores is having folder instances to store player data rather than a table because I just think it’s way easier. Is this possible to do with ProfileService in a safe way where I wouldn’t be contributing towards data loss?

When you get profiles in your script you can just make instances with it with a for loop

ListenToHopReady does not change any data and it listens to a profile being released as well so…

They already have some sort of “messaging service”, its called global updates

is there a way to do backups ? or rolebacks

https://madstudioroblox.github.io/ProfileService/api/#profilekeyinfo

1 Like

also i love this module,
but im supper confused in how to store the players data i followed the tutorials on top

i am working on a rpg game so players gonna have tons of items
and i store the item id and nescessary stats and estimating with allot margin that a single non stackable item slot would be around 1000 Bytes or 0.001 MB if my math was not wrong if i reserverd 50% of the key size i still have arround 2 Trillion slots to save potentialy 2 000 000 000
but when i test this in practice i only get to 13 000 items and it exceeds the 4MB max save limit of roblox in a key am i doing somthing wrong here i thought i could store waaaaay more then this ?

any ideas or fixes i could do to increase max items i only store needed data
like

playerData = {
	loginTimes = 0,
	playTime = 0,
	deaths = 0,
	kills = 0,
	itemsLooted = 0,
	rank = {},
	memberShip = {},
	penalty = {},
	flags = {},
	quests = {},
	coins = math.random(1,100000000),
	gems = 0,
	titles = {1},
	equipedTitle = 1,
	companion = {},
	config = {["renderDistance"]=5},
	-- stats {lvl,xp}
	lvl_main = {1,0},
	lvl_attack = {1,0},
	lvl_strength = {1,0},
	lvl_defense = {1,0},
	lvl_ranged = {1,0},
	lvl_prayer ={1,0},
	lvl_magic = {1,0},
	lvl_vitality = {1,0},
	lvl_crafting = {1,0},
	lvl_mining = {1,0},
	lvl_smithing = {1,0},
	lvl_fishing = {1,0},
	lvl_cooking = {1,0},
	lvl_firemaking = {1,0},
	lvl_woodcutting = {1,0},
	lvl_runecrafting = {1,0},
	lvl_dungeoneering = {1,0},
	lvl_fletching = {1,0},
	lvl_agility = {1,0},
	lvl_herblore = {1,0},
	lvl_thieving = {1,0},
	lvl_slayer = {1,0},
	lvl_farming = {1,0},
	lvl_construction = {1,0},
	lvl_hunter = {1,0},
	lvl_summoning = {1,0},
	--equipment order = id , type , amount/nil , float , equiped , args
	--				1=	1,helmet,nil(nostackable),0.123
	--				2=	1,recource,50(stackable),nil(nofloat)
	equipment = {
		[1]={1,"helmet",nil,math.random(0,1000000),true},
		[2]={1,"chestplate",nil,math.random(0,1000000),true},
		[3]={1,"pants",nil,math.random(0,1000000),true},
		[4]={1,"boots",nil,math.random(0,1000000),true},
		[5]={1,"sword",nil,math.random(0,1000000),false},
		[6]={1,"pickaxe",nil,math.random(0,1000000),nil,"$m:10,$a:1"},
		[7]={1,"log",50,nil},

		[8]={1,"cape",nil,math.random(0,1000000),nil,"$m:1"},
		
		[9]={1,"helmet",nil,math.random(0,1000000),false,"$m:5,$a:1"},
		[10]={1,"chestplate",nil,math.random(0,1000000),false,"$m:5"},
		[11]={1,"pants",nil,math.random(0,1000000),false,"$m:5"},
		[12]={1,"boots",nil,math.random(0,1000000),false,"$m:5"},
		
		[13]={1,"helmet",nil,math.random(0,1000000),false,"$m:6,$a:2"},
		[14]={1,"chestplate",nil,math.random(0,1000000),false,"$m:6"},
		[15]={1,"pants",nil,math.random(0,1000000),false,"$m:6"},
		[16]={1,"boots",nil,math.random(0,1000000),false,"$m:6"},
		
		[17]={1,"helmet",nil,math.random(0,1000000),false,"$m:7,$a:3"},
		[18]={1,"chestplate",nil,math.random(0,1000000),false,"$m:7"},
		[19]={1,"pants",nil,math.random(0,1000000),false,"$m:7"},
		[20]={1,"boots",nil,math.random(0,1000000),false,"$m:7"},
		
		[21]={1,"helmet",nil,math.random(0,1000000),false,"$m:8,$a:4"},
		[22]={1,"chestplate",nil,math.random(0,1000000),false,"$m:8"},
		[23]={1,"pants",nil,math.random(0,1000000),false,"$m:8"},
		[24]={1,"boots",nil,math.random(0,1000000),false,"$m:8"},
		
		[25]={1,"helmet",nil,math.random(0,1000000),false,"$m:9,$a:5"},
		[26]={1,"chestplate",nil,math.random(0,1000000),false,"$m:9"},
		[27]={1,"pants",nil,math.random(0,1000000),false,"$m:9"},
		[28]={1,"boots",nil,math.random(0,1000000),false,"$m:9"},
		
		[29]={1,"helmet",nil,math.random(0,1000000),false,"$m:10,$a:6"},
		[30]={1,"chestplate",nil,math.random(0,1000000),false,"$m:10"},
		[31]={1,"pants",nil,math.random(0,1000000),false,"$m:10"},
		[32]={1,"boots",nil,math.random(0,1000000),false,"$m:10"},
		
		[33]={1,"helmet",nil,math.random(0,1000000),false,"$m:11,$a:7"},
		[34]={1,"chestplate",nil,math.random(0,1000000),false,"$m:11"},
		[35]={1,"pants",nil,math.random(0,1000000),false,"$m:11"},
		[36]={1,"boots",nil,math.random(0,1000000),false,"$m:11"},
		
		[37]={1,"helmet",nil,math.random(0,1000000),false,"$m:12,$a:8"},
		[38]={1,"chestplate",nil,math.random(0,1000000),false,"$m:12"},
		[39]={1,"pants",nil,math.random(0,1000000),false,"$m:12"},
		[40]={1,"boots",nil,math.random(0,1000000),false,"$m:12"},
		
		[41]={1,"helmet",nil,math.random(0,1000000),false,"$m:13,$a:9"},
		[42]={1,"chestplate",nil,math.random(0,1000000),false,"$m:13"},
		[43]={1,"pants",nil,math.random(0,1000000),false,"$m:13"},
		[44]={1,"boots",nil,math.random(0,1000000),false,"$m:13"},
		
		[45]={1,"helmet",nil,math.random(0,1000000),false,"$m:1,$a:10"},
		[46]={2,"cape",nil,math.random(0,1000000),false},
		[47]={3,"cape",nil,math.random(0,1000000),false},
		[48]={4,"cape",nil,math.random(0,1000000),false},
		[49]={5,"cape",nil,math.random(0,1000000),false},
		[50]={6,"cape",nil,math.random(0,1000000),false},
		[51]={7,"cape",nil,math.random(0,1000000),false},
		[52]={8,"cape",nil,math.random(0,1000000),false},
	}
}

i just wanna know if i can do anithing to get atleast 1 million item slots somehow or not
i mean there are many simulator games with inf inventory space (i know its not unstackables) is there a way ot do i just have to live with the 13 k and just optimize it more ?

ps i know stackables are only one slot with an ammount number i can’t do that for equipment as there unique with a random FLOAT and possible unusual or material variant
sorry for long messy post

I can highly recommend this module to everyone. It takes on a difficult task and solves it without any problems. Not even the examples of Roblox to Datastores work as reliably as this module.
I will definitely use it for all my future games.

Thank you @loleris for creating this exiting module! :pray:

Hello I have a question. When ever I add a new value I want to save in my profilestore it never adds it to the player’s data when returning it.

local ProfileStore = ProfileService.GetProfileStore(
    "PlayerData.1",

    -- The data this store saves
    {
        Cash = 0,
        Cars = {}
    }
)

I added a new value called Cash and when I print the player’s data returned

it prints this

{
     Cars = {}
}

it does not list the Cash

How would I fix this.

You have to use reconcile on it.

I have multiple Profiles in my experience, however when I teleport, it releases the Profile, resulting in the primary

Profile:ListenToRelease()

to trigger. I want to use

Profile:ListenToHopReady()

to make sure that data doesn’t take more than 7 seconds to load when hopping between places.

Is it ok if I remove ListenToRelease from the PlayerAdded function?

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

local Name = "Characters"

local DefaultProfile = {}

local ProfileStore = ProfileService.GetProfileStore("OwnedCharacters", DefaultProfile)

local Profiles = {}

Players.PlayerAdded:Connect(function(Player)
	local Profile = ProfileStore:LoadProfileAsync("Player_"..Player.UserId)
	if Profile then
		Profile:AddUserId(Player.UserId)
		Profile:Reconcile()
		Profile:ListenToRelease(function()
			Profile[Player] = nil
			Player:Kick("/n Error Code 1: /n /n Data loaded from another server. Please close Roblox and try again.")
		end)
		if Player:IsDescendantOf(Players) then
			Profiles[Player] = Profile
			local Data = Instance.new("Folder")
			Data.Name = Name
			Data.Parent = Player
			
			local function Save()
				print("Saving")
				local DataForProfile = {}

				for _, Character in pairs(Data:GetChildren()) do
					local Skins = {}
					for Skin, Value in pairs(Character:GetAttributes()) do
						Skins[Skin] = Value
					end
					DataForProfile[Character.Name] = Skins
				end
				print("Ready to upload")
				Profile.Data = DataForProfile
				print("Saved")
			end

			for DataType, Value in pairs(Profile.Data) do
				local Character = Instance.new("BinaryStringValue")
				Character.Name = DataType
				Character.Parent = Data
				for CharacterSkin, Value in pairs(Value) do
					Character:SetAttribute(CharacterSkin, Value)
				end
				Character.AttributeChanged:Connect(Save)
			end
			Data.ChildAdded:Connect(Save)
		end
	end
end)

ReplicatedStorage.Teleporting.Event:Connect(function(Player)
	local Profile = Profiles[Player]
	if Profile then
		Profile:Release()
		Profile:ListenToHopReady(function()
			local Released = Player:WaitForChild("Released")
			local Value = Instance.new("BinaryStringValue")
			Value.Name = Name
			Value.Parent = Released
		end)
	end
end)

Players.PlayerRemoving:Connect(function(Player)
	local Profile = Profiles[Player]
	if Profile then
		Profile:Release()
	end
end)

Hello! i was wondering what is wrong with my script? im trying to save the givewarn function but nothing is working.

The datamanager

local Players = game:GetService("Players")
local ProfileService = require(game.ReplicatedStorage.ProfileService)
local BindableEvent = game.ReplicatedStorage:WaitForChild("RemoteEvents"):WaitForChild("SaidBadWord")

local ProfileStore = ProfileService.GetProfileStore(
    "Player",
    {
        Warnings = 0;
        Timemute = 0;
    }
)

local Profiles = {}
local function GiveWarn(player, amount)
    print("Working")
    local data = Profiles[player]
    if data ~= nil then
        player.Values.Warnings.Value = player.Values.Warnings.Value + 1
    else print(player .. ' was not found.') end
end


local function onPlayerAdded(player)
    local profile = ProfileStore:LoadProfileAsync(
        "Player_"..player.UserId,
        "ForceLoad"
    )

    if profile then
        profile:ListenToRelease(function()
            Profiles[player] = nil
            player:Kick()
        end)
        if player:IsDescendantOf(Players) then
            Profiles[player] = profile
            GiveWarn()    
        else
            profile:Release()
        end
    else
        player:Kick()
    end
end



local function onPlayerRemoving(player)
    local profile = Profiles[player]
    if profile then
        profile:Release()
    end
end

Players.PlayerAdded:Connect(onPlayerAdded)
Players.PlayerRemoving:Connect(onPlayerRemoving)
BindableEvent.Event:Connect(GiveWarn(1))

local DataManager = {}

function DataManager:Get(player)
    local profile = Profiles[player]
    if Profiles[player] then
        return profile.Data
    end
end


return DataManager

My output

 10:55:40.828  [ProfileService]: Roblox API services available - data will be saved  -  Server - ProfileService:2307
  10:55:41.280  Working  -  Server - DataManager:16
  10:55:41.280  ReplicatedStorage.DataManager:20: attempt to concatenate nil with string

The else line here will error if player is nil because you can’t concatenate a nil value with a string so I recommend replacing the print function with this

print("data was not found.")

Then, you run the GiveWarn function argument without passing any arguments, causing an error on the print function as i said earlier

1 Like

Hi! it fixed my errors but now im having a issue by it not saving the value

You should update the code sample:

for _, player in ipairs(Players:GetPlayers()) do
    coroutine.wrap(PlayerAdded)(player)
end

for _, player in ipairs(Players:GetPlayers()) do
    task.spawn(PlayerAdded, player)
end
2 Likes