Datastore Tutorial for Beginners

There are other tutorials regarding datastores with tools

I tried this and it kept outputting the error.

-- // Assigning variables //
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("MyDataStore") -- This can be changed to whatever you want

local function saveData(player) -- The functions that saves data

	local tableToSave = {
		player.leaderstats.Money.Value
	}

	local success, err = pcall(function()
		dataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
	end)

	if success then -- If the data has been saved
		print("Data has been saved!")
	else -- Else if the save failed
		print("Data hasn't been saved!")
		warn(err)		
	end
end

game.Players.PlayerAdded:Connect(function(player) -- When a player joins the game

	-- // Assigning player stats //
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local Playtime = Instance.new("IntValue")
	Playtime.Name = "Playtime"
	Playtime.Parent = leaderstats

	local data -- We will define the data here so we can use it later, this data is the table we saved
	local success, err = pcall(function()

		data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore

	end)

	if success then -- If there were no errors and player loaded the data

		Playtime.Value = data[1] -- Set the money to the first value of the table (data)

	else -- The player didn't load in the data, and probably is a new player
		print("The player has no data!") -- The default will be set to 0
	end

end)

game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
	local success, err  = pcall(function()
		saveData(player) -- Save the data
	end)

	if success then
		print("Data has been saved")
	else
		print("Data has not been saved!")
	end
end)

game:BindToClose(function() -- When the server shuts down
	for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
		local success, err  = pcall(function()
			saveData(player) -- Save the data
		end)

		if success then
			print("Data has been saved")
		else
			print("Data has not been saved!")
		end
	end
end)

By error, I mean the output says data has not been saved.


That is every time I play, on studio, and on roblox…

I do have API services enabled…

2 Likes

Simply print the error to allow you to debug it.

Sorry I don’t know what you mean. Could you explain that

The second value returned by a pcall is the error message or the value returned by the function it ran. And so, you could do this:

local Success, Var = pcall(function()
    table.sort(nil) -- Would throw an error
end)

if not Success and Var then
    error(Var)
end

@DevHelpAccount , no. You need to edit the script, let me just do it for you:

-- // Assigning variables //
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("MyDataStore") -- This can be changed to whatever you want

local function saveData(player) -- The functions that saves data

	local tableToSave = {
		player.leaderstats.Money.Value
	}

	local success, err = pcall(function()
		dataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
	end)

	if success then -- If the data has been saved
		print("Data has been saved!")
	else -- Else if the save failed
		print("Data hasn't been saved!")
		warn(err)		
	end
end

game.Players.PlayerAdded:Connect(function(player) -- When a player joins the game

	-- // Assigning player stats //
	local leaderstats = Instance.new("Folder")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = player

	local Playtime = Instance.new("IntValue")
	Playtime.Name = "Playtime"
	Playtime.Parent = leaderstats

	local data -- We will define the data here so we can use it later, this data is the table we saved
	local success, err = pcall(function()

		data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore

	end)

	if success then -- If there were no errors and player loaded the data

		Playtime.Value = data[1] -- Set the money to the first value of the table (data)

	else -- The player didn't load in the data, and probably is a new player
		print("The player has no data!") -- The default will be set to 0
	end

end)

game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
	local success, err  = pcall(function()
		saveData(player) -- Save the data
	end)

	if success then
		print("Data has been saved")
	else
		print("Data has not been saved!")
	end
end)

game:BindToClose(function() -- When the server shuts down
	for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
		local success, err  = pcall(function()
			saveData(player) -- Save the data
		end)

		if success then
			print("Data has been saved")
		else
			print("Data has not been saved!")
            error(err)
		end
	end
end)

Do I just run that in the command bar?

It says that Money is not a valid member of my player. I use Playtime on mine

Sorry for late response, just change Money with Playtime in the script

1 Like

Don’t worry! I have it working! Thanks anyway! :grin:

Edit: Oh, and not “anyway” just thanks for this amazing datastore tutorial! Recommend it!

:grin:

For some reason, I learnt more about I, v in pairs and tables then I did for datastores :grin:

1 Like

I made this mistake too, At the top in the local tabelToSave it says player.leaderstats. Money . value, the mistake you made is you re-named the Money to “Playtime”, all you have to is change player.leaderstats. Money . value to: player.leaderstats.Playtime.value and then it should be fixed.
Hope this helps! :coefficients:

I have a question about that, and is that how it would prevent the loss of data? It is simply expelling it, and when it enters again, what will happen?

Basically, we are kicking the player if the data fails to load. There are two situations where the data returned will be nil: If it fails to load and when the data is just empty (the player is playing for the first time / there’s no previous data). We must deal with the difference between those two situations to avoid dataloss (this is not the only way to avoid it), here is an example:

You might have seen things like this on some bad datastore tutorials:

local DefaultCash = 10
local PlayerCash = CashDatastore:GetAsync(player_id) or DefaultCash

Well, it works. If the player has no cash stored, meaning they’re playing for the first time, :GetAsync method will return nil, and we use the default value in this case. But there’s a huge problem here, what if the data fails to load? Simple, :GetAsync will return nil, and we are going to reset the player’s cash. Imagine someone playing for a long time to get a good amount of cash and for an error like this, their cash is reset.

This is why I’m kicking the player and not saving the data (< this is important) if it fails to load. There are many others solutions for this case, like retry to load the data, rejoin the player with TeleportService, you can also think about a new solution, just use your imagination and don’t forget to test if it actually works.

I hope this makes sense :slight_smile:

1 Like

Well, I have been based on two DataStores that I have seen in the developer forum, and then I tried to combine a little of both. Use the GEILER123456 method to save the data and use BindClose, then use the part of your method to kick the player if there is possible data loss; After all this, I don’t know if my DataStore will work fine

local DataStoreService = game:GetService("DataStoreService")
local configurationsData = DataStoreService:GetDataStore("ConfigurationsData")

Players.PlayerAdded:Connect(function(player)
	local DataFolder = Instance.new("Configuration")
	DataFolder.Name = "SettingsData"
	DataFolder.Parent = player
	local GraphicsFolder = Instance.new("Folder")
	GraphicsFolder.Name = "Graphics"
	GraphicsFolder.Parent = DataFolder
	local GameFolder = Instance.new("Folder")
	GameFolder.Name = "GameSettings"
	GameFolder.Parent = DataFolder
	local KeysFolder = Instance.new("Folder")
	KeysFolder.Name = "Keys"
	KeysFolder.Parent = DataFolder

	local success, KeyInfo = pcall(function()
		return configurationsData:GetAsync(player.UserId)
	end)
	if not success then
		warn(KeyInfo)
		player:Kick("Has sido expulsado del juego para evitar la pérdida de datos, ingrese de nuevo / You have been kicked from the game to avoid data loss, please log in again.")
	elseif success then
		if KeyInfo ~= nil then
			GraphicsFolder:WaitForChild("Shadow").Value = KeyInfo.shadows
			shadowSettings[KeyInfo.shadows ](player)
			GraphicsFolder:WaitForChild("Water").Value = KeyInfo.water
			waterSettings[KeyInfo.water](player)
			GraphicsFolder:WaitForChild("Tree").Value = KeyInfo.tree_texture
			treeSettings[KeyInfo.tree_texture](player)

			GameFolder:WaitForChild("FPS").Value = KeyInfo.fps
			ViewFPS[KeyInfo.fps](player)
			GameFolder:WaitForChild("Ping").Value = KeyInfo.ping
			ViewPing[KeyInfo.ping](player)
			GameFolder:WaitForChild("BrilloVol").Value = KeyInfo.gamma
			VolumenDeBrillo["Gamma"](player, KeyInfo.gamma)
			GameFolder:WaitForChild("PlayerChose").Value = KeyInfo.friends
			LumberDrop[KeyInfo.friends](player)
			GameFolder:WaitForChild("Language").Value = KeyInfo.language
			LanguageConfg[KeyInfo.language](player, "PlayerAdded")
			GameFolder:WaitForChild("SlotsInventory").Value = KeyInfo.SizeSlots
			SlotsInventory["SlotsSize"](player, KeyInfo.SizeSlots)

			KeysFolder:WaitForChild("Crouch").Value = KeyInfo.crouch
			CrouchKey[KeyInfo.crouch](player)
		else
			GraphicsFolder:WaitForChild("Shadow").Value = "Medio"
			shadowSettings.Medio(player)
			GraphicsFolder:WaitForChild("Water").Value = "Medio"
			waterSettings.Medio(player)
			GraphicsFolder:WaitForChild("Tree").Value = "Enabled"
			treeSettings.Enabled(player)

			GameFolder:WaitForChild("FPS").Value = "UnEnabled"
			ViewFPS.UnEnabled(player)
			GameFolder:WaitForChild("Ping").Value = "UnEnabledPing"
			ViewPing.UnEnabledPing(player)
			GameFolder:WaitForChild("BrilloVol").Value = 0.5
			VolumenDeBrillo["Gamma"](player, 0.5)
			GameFolder:WaitForChild("PlayerChose").Value = "ForLocalPlayer"
			LumberDrop.ForLocalPlayer(player)
			GameFolder:WaitForChild("Language").Value = "English"
			LanguageConfg.English(player)
			GameFolder:WaitForChild("SlotsInventory").Value = 90
			SlotsInventory["SlotsSize"](player, 90)

			KeysFolder:WaitForChild("Crouch").Value = "OnePress"
			CrouchKey.OnePress(player)
		end
	end
end)

local function save(player, dontWait)
	local SettingsData = player:FindFirstChild("SettingsData")
	if SettingsData then
		local Graphics = SettingsData:FindFirstChild("Graphics")
		local GameSettings = SettingsData:FindFirstChild("GameSettings")
		local Keys = SettingsData:FindFirstChild("Keys")
		if Graphics and GameSettings and Keys then

			local Send_Data = {
				shadows = player.SettingsData.Graphics.Shadow.Value;
				water = player.SettingsData.Graphics.Water.Value;
				tree_texture = player.SettingsData.Graphics.Tree.Value;

				fps =player.SettingsData.GameSettings.FPS.Value;
				ping = player.SettingsData.GameSettings.Ping.Value;
				gamma = player.SettingsData.GameSettings.BrilloVol.Value;
				friends = player.SettingsData.GameSettings.PlayerChose.Value;
				language = player.SettingsData.GameSettings.Language.Value;
				SizeSlots = player.SettingsData.GameSettings.SlotsInventory.Value;

				crouch = player.SettingsData.Keys.Crouch.Value;
			}
			local Success,Error
			repeat
				Success,Error = pcall(configurationsData.UpdateAsync, configurationsData, player.UserId,function(Data)
					return Send_Data
				end)
			until Success 
			if Success then
				print("Se ha guardado correctamente / Has been saved successfully")
			else
				print("hubo un error al guardar / there was an error saving")
				warn(Error)
			end
		end	
	end
end

game:BindToClose(function()
	if RunService:IsStudio() then
		task.wait(2)
	else
		local finished = Instance.new("BindableEvent")
		local allPlayers = Players:GetPlayers()
		local leftPlayers = #allPlayers

		for _, player in pairs(allPlayers) do
			coroutine.wrap(function()
				save(player)
				leftPlayers -= 1
				if leftPlayers == 0  then
					finished:Fire()
				end
			end)()
		end
		finished.Event:Wait()
	end
end)

Players.PlayerRemoving:Connect(save)
1 Like

I changed it to fit my values and it gave me a error saying DataStore request was added to queue. If request queue fills, further requests will be dropped. Try sending fewer requests

The code provided no longer works.

Running it presents you with a data save fail and Unable to cast value to function.

The code works perfectly fine for me,

Can you send me the line where it errors?

1 Like

Hi, the code has one more end, check the output.


Sorry for replying so late but this tutorial worked perfectly for my games. Thanks @DevTest_2hd235!

1 Like

Hello.
I did this, and it works but seems a bit hardcoded as I must save as like 14 different items.
I tried to use for-loops to do this but it isn’t working.
Code:


-- // Assigning variables //
local DataStoreService = game:GetService("DataStoreService")
local dataStore = DataStoreService:GetDataStore("PlaneSkinOwnedSave") -- This can be changed to whatever you want

local function saveData(player) -- The functions that saves data
	local skins = player.OwnedSkins
	tableToSave = {
	}
	for i,v in pairs(skins:GetDescendants()) do
		if v.Name == "Owned" then
			table.insert(tableToSave, v.Value)
			print(v.Value)
		end
	end
	local success, err = pcall(function()
		dataStore:SetAsync(player.UserId, tableToSave) -- Save the data with the player UserId, and the table we wanna save
	end)

	if success then -- If the data has been saved
		print("Data has been saved!")
	else -- Else if the save failed
		print("Data hasn't been saved!")
		warn(err)		
	end
end
game.Players.PlayerAdded:Connect(function(player) -- When a player joins the game
	wait(1)
	-- // Assigning player stats //
	local skins = player.OwnedSkins
	local toGive = {}
	for i,v in pairs(skins:GetDescendants()) do
		if v.Name == "Owned" then
			table.insert(toGive, v.Value)
		end
	end
	local data -- We will define the data here so we can use it later, this data is the table we saved
	local success, err = pcall(function()
		data = dataStore:GetAsync(player.UserId) -- Get the data from the datastore
	end)
	if success and data then -- If there were no errors and player loaded the data
		for count = 1,14 do
			print(data[count])
			toGive[count] = data[count]
		end
	else -- The player didn't load in the data, and probably is a new player
		print("The player has no data!") -- The default will be set to 0
	end

end)
game.Players.PlayerRemoving:Connect(function(player) -- When a player leaves
	local success, err  = pcall(function()
		saveData(player) -- Save the data
	end)
	if success then
		print("Data has been saved")
	else
		print("Data has not been saved!")
	end
end)
game:BindToClose(function() -- When the server shuts down
	for _, player in pairs(game.Players:GetPlayers()) do -- Loop through all the players
		local success, err  = pcall(function()
			saveData(player) -- Save the data
		end)
		if success then
			print("Data has been saved")
		else
			print("Data has not been saved!")
		end
	end
end)

No errors in dev log