OwlDataIntermediate

Player Profiles with OwlData

Loading a profile on join, syncing leaderstats reactively and awarding currency safely.

OwlDataObserveIncrement
ServerScriptService/Services/StatsService.lua
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
local Owl = require(ReplicatedStorage.OwlKnit.Owl)
local OwlData = Owl.Util.Data
 
local DEFAULT_SCHEMA = { Coins = 0, Level = 1 }
 
local StatsService = Owl.CreateService({ Name = "StatsService" })
 
function StatsService:OwlInit()
end
 
function StatsService:OwlStart()
end
 
function StatsService:OwlOnPlayerAdded(plr: Player)
    OwlData.Load(plr, {
        storeName = "PlayerStats_v1",
        schema = DEFAULT_SCHEMA,
    }):andThen(function(profile)
        local leaderstats = Instance.new("Folder")
        leaderstats.Name = "leaderstats"
        leaderstats.Parent = plr
 
        local coins = Instance.new("IntValue")
        coins.Name = "Coins"
        coins.Parent = leaderstats
 
        -- > // Keeps leaderstats in sync with the profile automatically
        profile:Observe("Coins", function(value)
            coins.Value = value
        end)
    end):catch(warn)
end
 
function StatsService:AwardCoins(plr: Player, amount: number)
    local profile = OwlData.Get(plr)
    if not profile then return end
 
    profile:Increment("Coins", amount)
end
 
return StatsService
What's happening here
  • profile:Observe("Coins", fn) fires immediately with the loaded value, so leaderstats.Coins is correct from the very first frame.
  • OwlData.Get(plr) inside AwardCoins returns nil safely if the profile somehow isn't loaded yet no risk of indexing nil.
  • profile:Increment is atomic safe to call from multiple places without a manual read-then-write race.