Lately I've seen people having problems using DataStores. They're a farily simple, yet powerful tool to use for your games. There are many uses for them including, but not limited to;

  • Saving player stats
  • Admin/Ban system
  • Game stats (amount of players played in a day, average play time, etc.)
  • Cross-server chat

For this guide, I'll do game stats since they're easy to record.


Setting Up the DataStore


To get the DataStore, you use DataStoreService and DataStoreService:GetDataSore(string name, string scope = "global").

local DataStoreService = game:GetService("DataStoreService")
local GameStats = DataStoreService:GetDataStore("GameStats")

GetDataStore will return a DataStore (obv.) with the given name and scope containing all of the data saved to it.


Saving and Loading


DataStore has a per place limit seen here; DataStore limitations

Being mindful of the amount of requests you make is important to keeping data safe. With this in mind, saving data every time something changes probably isn't a good idea if it changes a lot and can be stored elsewhere, then saved later (all in one go).

Loading

To get the data from the DataStore, you use DataStore:GetAsync(string key).

The key is important and should be easy to distinguish between each player -- if what you're saving is for each player. For example, if you're saving the player's leaderstats, the key should be something like user_(UserId). It shouldn't be a player's username since they can change it and their data would be lost if you used it as a key.

Now for getting the scripting part;
When you use GetAsync, you may also want to make sure there's something being returned, otherwise you may be setting nil values.

local DataStoreService = game:GetService("DataStoreService")
local GameStats = DataStoreService:GetDataStore("GameStats")
local Data = GameStats:GetAsync(game.PlaceId) --// This key is the PlaceId (it will never change).

if Data then
    print(game.PlaceId.." has data.")
else
    warn("No data found in "..game.PlaceId.." or it is the first time being played.")
end

If the game is being played for the first time, there won't be anything in the DataStore, so Data will not return anything, which is what we checked for. (That's a good stat to add :stuck_out_tongue:). Let's create a table with the stuff we'd like to save in the DataStore. We should also put our if statement inside a PlayerAdded function with GetAsync inside so it returns new data.

local DataStoreService = game:GetService("DataStoreService")
local GameStats = DataStoreService:GetDataStore("GameStats")

local StatsToSave = {["PlayingForTheFirstTime"] = true, ["TimesPlayed"] = 0, ["TotalTimePlayed"] = 0} --// These are all the stats we will save

game.Players.PlayerAdded:Connect(function()
    local Data = GameStats:GetAsync(game.PlaceId) --// This key is the PlaceId (it will never change). This will return the table we save to the DataStore.
    if Data then
        print(game.PlaceId.." has data.")
        StatsToSave.PlayingForTheFirstTime = Data.PlayingForTheFirstTime
        StatsToSave.TimesPlayed = Data.TimesPlayed + StatsToSave + 1 --// Total amount of times played + the new player

    spawn(function() --// We don't want the loop to stop the rest of the script from running
        while true do
            StatsToSave.TotalTimePlayed = (Data ~= nil and Data.TotalTimePlayed + 
    StatsToSave.TotalTimePlayed + 1) or StatsToSave.TotalTimePlayed + 1 --// We have to make sure we aren't overwriting the old stats, and we have to make sure we're adding the new time to the old time.
            wait(1)
        end
    end)

    else
        warn("No data found in "..game.PlaceId.." or it is the first time being played.")
        StatsToSave.PlayingForTheFirstTime = false
        StatsToSave.TimesPlayed = StatsToSave.TimesPlayed + 1 --// Add 1 to the total amount of times played
        spawn(function() --// We don't want the loop to stop the rest of the script from running
            while true do
                    StatsToSave.TotalTimePlayed = (Data ~= nil and Data.TotalTimePlayed + 
        StatsToSave.TotalTimePlayed + 1) or StatsToSave.TotalTimePlayed + 1 --// We have to make sure we aren't overwriting the old stats, and we have to make sure we're adding the new time to the old time.
                wait(1)
            end
        end)

    end
end)

Now we have the loading part done.


Once a player leaves, we are done gathering information from that player. We can now save everything we've gathered to the DataStore.


Saving

To save the data to the DataStore, there are two methods; DataStore:SetAsync(string key, Variant value) and DataStore:UpdateAsync(string key, function<Variant>(Variant) transformFunction).

The one you want to use depends on what's being saved, how often it's being saved, and if what's being saved can be overwritten. GetAsync sometimes returns cached data, and other servers may have modified the values. In my case, I would recommend using UpdateAsync since the values are being constantly changed when players enter and leave. This time we can put UpdateAsync inside a PlayerRemoving function.

local DataStoreService = game:GetService("DataStoreService")
local GameStats = DataStoreService:GetDataStore("GameStats")

local StatsToSave = {["PlayingForTheFirstTime"] = true, ["TimesPlayed"] = 0, ["TotalTimePlayed"] = 0} --// These are all the stats we will save

game.Players.PlayerAdded:Connect(function()
    local Data = GameStats:GetAsync(game.PlaceId) --// This key is the PlaceId (it will never change). This will return the table we save to the DataStore.
    if Data then
        print(game.PlaceId.." has data.")
        StatsToSave.PlayingForTheFirstTime = Data.PlayingForTheFirstTime
        StatsToSave.TimesPlayed = Data.TimesPlayed + StatsToSave + 1 --// Total amount of times played + the new player

    spawn(function() --// We don't want the loop to stop the rest of the script from running
        while true do
            StatsToSave.TotalTimePlayed = (Data ~= nil and Data.TotalTimePlayed + 
    StatsToSave.TotalTimePlayed + 1) or StatsToSave.TotalTimePlayed + 1 --// We have to make sure we aren't overwriting the old stats, and we have to make sure we're adding the new time to the old time.
            wait(1)
        end
    end)

    else
        warn("No data found in "..game.PlaceId.." or it is the first time being played.")
        StatsToSave.PlayingForTheFirstTime = false
        StatsToSave.TimesPlayed = StatsToSave.TimesPlayed + 1 --// Add 1 to the total amount of times played
        spawn(function() --// We don't want the loop to stop the rest of the script from running
            while true do
                    StatsToSave.TotalTimePlayed = (Data ~= nil and Data.TotalTimePlayed + 
        StatsToSave.TotalTimePlayed + 1) or StatsToSave.TotalTimePlayed + 1 --// We have to make sure we aren't overwriting the old stats, and we have to make sure we're adding the new time to the old time.
                wait(1)
            end
        end)

    end
end)

game.Players.PlayerRemoving:Connect(function()
    GameStats:UpdateAsync(game.PlaceId, function()
        return StatsToSave --// This is what we're saving to the DataStore
    end)
end)

And we are done. This will load the game's data when a player joins, and save when a player leaves. :smile:

Please note there may be errors that I missed to due the lack of a computer to test this on. Sorry if there is.