Skip to content

Leaderstats

Canonical copy (three files): Docs → Example: Leaderstats.

Roblox shows the player list from a Folder named exactly leaderstats under the Player. This service creates those values, then mirrors DataService coins into them.

It does not call DataService.Init. Boot that in Data boot with your PlayerData Template.

FileRole
shared/PlayerData.clhTemplate structs (Currencies.Coins, inventory LuaArray)
server/LeaderstatsServer.clhstruct LeaderstatsServer — fields + method decls (same stem)
server/LeaderstatsServer.server.clppClass:: bodies + void init()
RuleWhy
Same-stem headerLeaderstatsServer.clh + LeaderstatsServer.server.clpp. A differently named leaderstats.clh is a require.
.server.clppUntagged cpp is a ModuleScript; init() will not run by itself.
Paths.Currencies.CoinsAfter Init. A local int Coins is not a Data path.
string_concat or string +Luau ... Do not write .. in the .cpp.
Janitor keyed by player nameLeaving the game must Destroy the folder.
Lambda or named functionGetChangedSignal(...).Connect([coinsValue](int n) { ... }) is valid.
#pragma once
#include <clpp/roblox.clh>
#include <clpp/libs/sweep.clh>
#include <clpp/libs/keep.clh>
struct LeaderstatsServer {
static constexpr int STARTING_COINS = 0;
Janitor janitor;
string GetPlayerJanitorKey(Player player);
void UpdateLeaderstatsWithValues(IntValue currentValue, int newValue);
Folder EnsurePlayerLeaderstatsFolder(Player player);
void PlayerEntered(Player player);
};
void LeaderstatsServer::PlayerEntered(Player player) {
Folder leaderstatsFolder = EnsurePlayerLeaderstatsFolder(player);
Data playerData = DataService.Server.WaitFor(player);
IntValue coinsValue = static_cast<IntValue>(leaderstatsFolder.FindFirstChild("Coins"));
if (coinsValue) {
playerData.GetChangedSignal(DataService.Server.Paths.Currencies.Coins)~>Connect(func (int newValue) {
UpdateLeaderstatsWithValues(coinsValue, newValue);
}
);
}
janitor.Add(leaderstatsFolder, "Destroy", GetPlayerJanitorKey(player));
}

init() constructs LeaderstatsServer, assigns janitor = new Janitor(), runs PlayerEntered for everyone already in the game, then PlayerAdded + BindToClose. Full listing is on the handbook page.