Profiles & Sessions
Every loading and waiting operation in OwlData is Promise-based, keeping the async code clean and avoiding unexpected thread yields.
Loading a profile
OwlData.Load(plr: Player, opts: table): PromiseStarts the loading cycle for a player's profile. opts accepts:
| Field | Type | Description |
|---|---|---|
storeName |
string |
Unique DataStore name |
schema |
table |
Default data structure for a brand-new player |
migrations |
table (optional) |
Dictionary keyed by version number, for upgrading old data structures |
OwlData.Load(plr, {
storeName = "PlayerData_v1",
schema = DEFAULT_SCHEMA,
}):andThen(function(profile)
-- > // The profile is loaded and ready to use
end):catch(function(err)
-- > // Something went wrong (Roblox outage, lock contention...)
end)Built-in safety: if a player leaves while their loading promise is still resolving, OwlData intercepts the event, ends the session (release the lock) to avoid leaving the profile corrupted or stuck in limbo and rejects the promise cleanly.
OwlData.Get(plr: Player): OwlProfile?Instantly returns the player's profile if it's already loaded. Returns nil if the profile isn't ready yet or the player isn't in the server.
OwlData.Await(plr: Player, timeout: number?): PromiseFor any script that needs a player's data without knowing whether it's finished loading yet resolves as soon as the profile becomes active. The optional timeout prevents the promise from hanging indefinitely if loading never completes.
The OwlOnProfileLoaded hook
To save you from writing the same "wait for the profile" boilerplate in every Service, OwlData plugs directly into Owl's lifecycle system. The moment a player's profile becomes ready, OwlData walks every registered Service any one with a method named OwlOnProfileLoaded has it invoked automatically, on its own thread (task.spawn), as (self, plr, profile).
local Owl = require(game.ReplicatedStorage.OwlKnit)
local QuestService = Owl.CreateService({ Name = "QuestService" })
function QuestService:OwlOnProfileLoaded(plr: Player, profile)
local activeQuests = profile:Get("Quests.InProgress")
print(("[QuestService] Loading %s's quests (%d in progress)"):format(plr.Name, #activeQuests))
end
return QuestServiceThe observation system
One of OwlProfile's most useful features is binding a callback to a key's changes in real time via :Observe():
local disconnect = profile:Observe("Stats.Level", function(newValue, oldValue)
print("Player leveled up!", oldValue, "->", newValue)
end)
-- > // To stop listening:
disconnect()Deferred first call: when you register an observer on a specific key (not the wildcard below), the callback runs once immediately deferred via task.defer with the key's current value. That means you never need a separate call to set up the initial state, whether that's populating a UI label or creating leaderstats on join.
Wildcard observation ("*"): passing "*" as the key creates a global observer that fires on any key change anywhere in the profile. Its callback signature is different (key, newValue, oldValue) since it needs to tell you which key actually changed.
Session security
OwlData enforces strict guarantees to eliminate item duplication and data corruption:
- Session locking. In
ProfileStoremode, session locking is native to Roblox's own database layer: if a player switches servers unusually fast, the new server attempts to load the data but waits asynchronously (via repeated requests handled by the adapter) for the old server to finish saving and release its lock. InDataStore2mode, OwlData simulates session locking through a secondary DataStore dedicated to locks (storeName .. "_Locks"), storing a token with the server's JobId and a timestamp, if another active server holds the lock, access is blocked and retried until the lock expires (60 seconds). - Safety disconnection. If a player's session ends unexpectedly at the global level for instance, the server loses ownership of the lock after a major network outage the adapter immediately cuts off local access to the profile and kicks the player as a safety measure, protecting their inventory's integrity rather than risking a write against a profile the server no longer safely owns.
This is what "protection against duplicate connections" ultimately buys you: two servers can never both believe they hold a valid, writable copy of the same player's profile at the same time.
Where to go next
- Overview - the full
OwlProfileread/write API (Get,Set,Update,Increment...) used throughout this page. - Architecture - how
ProfileStoreAdapterandDataStoreAdaptereach implement the session-locking behavior described above.