Skip to content

Leaderstats

Leaderstats and Leaderboards are easily confused by name alone, but they solve different problems. Leaderstats is the simple, per-player leaderstats folder that Roblox’s own client UI reads — the stats shown above a player’s head and in the player list, always live, never persisted by this module itself. Leaderboards is a persisted, queryable, cross-server ranking system — a global top-N over OrderedDataStores or a custom backend. If you want a global ranked list instead of a per-server display, that’s Leaderboards, not this module.

Roblox’s player-list leaderboard is convention, not API: a Folder named leaderstats under the player, with Value instances inside. Leaderstats owns that wiring against the kernel’s session lifecycle:

  1. kernel:onSession — on every session start, a leaderstats folder is created (or your GetContainer supplies one), and one Value instance is created per declared stat, seeded from data.
  2. A slow sweep (every UpdateSeconds, default 1s, Background priority) copies current data values into the Value instances. You never push updates — write your data, the leaderboard follows within a second.
  3. session:bind — cleanup runs when the session ends. If the module created the folder it destroys it; if you supplied GetContainer it destroys only the Value instances it made.

Each stat reads from the session’s profile data (session.Profile.Data, the persisted record from DataDriver) or the session’s transient data (session.Data). The default is profile if one exists, else session — persisted stats show persisted values without configuration.

local Root = game:GetService("ServerScriptService").ChloeKernelServer
local Leaderstats = require(Root.Leaderstats)
return function(kernel)
Leaderstats.attach(kernel, {
Coins = { Field = "Coins" }, -- from session.Profile.Data
Stage = { Field = "BestStage" },
Title = { Field = "Title", Kind = "StringValue", From = "Session" },
})
end

That is the whole integration. session.Profile.Data.Coins += 100 shows on the leaderboard within the next sweep.

Leaderstats.attach(kernel, stats, options?)stats maps display names (what the player list shows) to:

Field Default Description
Field required The data field to read
From Profile if present, else Session "Profile" (persisted, session.Profile.Data) or "Session" (transient, session.Data)
Kind "IntValue" "IntValue", "NumberValue", or "StringValue"

Options:

Option Default Description
UpdateSeconds 1 Sweep cadence
GetContainer creates leaderstats (player) → Instance — supply your own container (e.g. a folder another system also writes to). The module then only owns its Value instances
SkipLoop false Don’t schedule the sweep (specs call sweep() by hand)
Member Description
Leaderstats.attach(kernel, stats, options?) → handle Wire the mapping; hooks session start/end immediately
handle.sweep() Run one update pass by hand
handle.detach() Cancel the sweep, disconnect the session hook, clean up every tracked container/value