Overview

OwlData is OwlKnit's official persistence subsystem rewritten from scratch to be modern, asynchronous and decoupled. It acts as a unified interface between your game logic and whichever Roblox data-storage backend actually ends up saving the bytes.

Configuration

OwlData is fully self-contained: it's injected and initialized internally the moment Owl.Start() runs. Configuring persistence is just passing a Data table alongside the rest of your framework config.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Owl = require(ReplicatedStorage.OwlKnit.Owl)
 
Owl.Start({
    Data = {
        Backend = "ProfileStore", -- > // valid options: "ProfileStore" or "DataStore2"
        AutoSave = true, -- > // enables/disables the automatic save loop
        SaveInterval = 60, -- > // automatic save interval, in seconds
    }
})

What happens at startup:

  1. Environment check - OwlData immediately validates it's running on the server. A hard error is thrown if a client script tries to reach it.
  2. AutoSave loop - if AutoSave is on, OwlData uses Heartbeat combined with a Trove to run regular save cycles without blocking the main thread or leaking memory over a long session.
  3. Server shutdown - OwlData listens for the server closing to trigger an immediate, synchronous save of every active profile via its internal :SaveAll() method so a sudden shutdown doesn't cost players their last few minutes of progress.

The OwlProfile API

Once a Load or Await promise resolves (see Profiles & Sessions), you get back an OwlProfile object a wrapper around the raw data table with high-level utility methods, so you're rarely reaching into profile.Data directly.

Reading

Profile:Get(key: string): any - reads a value with dot notation for descending into nested tables without risking a nil-indexing error:

local level = profile:Get("Stats.Level") -- > // equivalent to profile.Data.Stats.Level

Profile:GetAll(): table - returns a shallow copy of the entire profile's data. Useful for sending a full snapshot or bulk reads.

Writing

Profile:Set(key: string, value: any) - sets or replaces a value at a key, dot notation supported. Intermediate tables are created on the fly if they don't exist yet. Notifies any observers of that key instantly (see Profiles & Sessions for the observation system).

profile:Set("Stats.Experience", 1500)

Profile:Update(key: string, fn: (oldValue: any) -> any) - modifies a value atomically through a callback, rather than a separate read-then-write:

profile:Update("Stats.Level", function(currentLevel)
    return currentLevel + 1
end)

Arithmetic and array utilities

  • Profile:Increment(key, amount?) - increments a numeric key by amount (default 1). Throws if the key isn't a number.
  • Profile:Decrement(key, amount?) - same, in reverse.
  • Profile:Append(key, value) - pushes a value onto the end of an array-shaped table:
    profile:Append("Inventory.Badges", "Goblin_Hunter")
  • Profile:Remove(key, value) - walks the array at key and removes every occurrence matching value, re-indexing the table cleanly afterward.

Profile state

  • Profile:IsLoaded(): boolean - whether the profile is currently active and safe to modify.
  • Profile:IsReleased(): boolean - whether the session has been closed (typically when the player leaves). Reading or writing a released profile logs a security warning rather than silently doing nothing.

Where to go next

  • Architecture - the adapter system behind Backend = "ProfileStore" | "DataStore2", and automatic fallback.
  • Profiles & Sessions - Load/Get/Await, the automatic OwlOnProfileLoaded hook and protection against duplicate sessions.