pithy add leaderboardNo required prerequisites. Add auth if it is not already there — a score belongs to a player, and server-authoritative writes need a scope on a session.
What lands in your repo
apps/<worker>/pithy.config.ts gains the registration, with an example board you are meant to replace:
leaderboard({
boards: [{ key: "high-scores", direction: "desc" }],
rank: "live",
serverAuthoritative: true,
}),apps/<worker>/wrangler.jsonc gains the DB binding, in every environment stanza. That is the only binding — no Workflow, unless you choose materialized ranking.
One migration runs, creating the board and entry tables.
Boards are config, not rows
That is the load-bearing decision. A board’s key, its direction and its window are policy: they should be diffable in git and they should not be mutable at runtime.
The example is an all-time board where the highest score wins — the smallest board that works. Yours might be:
boards: [
{ key: "high-scores", direction: "desc" },
{ key: "weekly", direction: "desc", window: "0 0 * * 1" },
{ key: "fastest-time", direction: "asc" },
],window is a cron expression, and that is the whole windowing model. Daily, weekly, calendar-month and calendar-year are all just cron strings, and so is a season that runs the first Monday of each quarter. A board with no window is all-time.
direction decides what winning means. desc for a score, asc for a time. Getting it wrong makes the worst player first, which is at least obvious.
A board key is effectively permanent. It is stored on every entry, so renaming one orphans its history.
Choosing how rank is computed
rank | Behavior |
|---|---|
"live" | Counted per request. Always correct, free under roughly ten thousand players per board, quadratic past that |
{ materialize: "<cron>" } | Recomputed on that schedule. Constant per request, as stale as the cadence |
Start with live. It is correct, it costs nothing at the sizes most games reach, and the threshold where it stops being free is a number rather than a feeling.
Switching to materialized ranking adds a Workflow, which means the binding cannot be written by add — wrangler requires a name and a class name on every entry and the deployed name is per environment. It arrives with provisioning, the same way every scheduled job in the kit does.
serverAuthoritative is on, and should stay on
With it on, posting a score requires the submit scope, minted for your own server-side code. Your Worker decides what the score was and writes it.
With it off, a client posts its own score. Every platform that offers the choice makes the safe option opt-in, and every game that took the default has a top ten full of impossible numbers.
Turn it off only if your scoring genuinely happens on the client and you have decided you accept that.
Publishing your first score
From your own route, after your own logic decided what happened:
await leaderboard.submit({
board: "high-scores",
userId: c.var.auth.userId,
score: finalScore,
});A player’s entry in a window is their best score, not their most recent — a worse score does not displace a better one.
Reading a board
Standings come back paginated, with the caller’s own rank available whether or not they are on the page you asked for. You are 412th is the number a player actually wants, and paging through four hundred rows to find it is the thing this avoids.
Check it worked
pithy doctor reports leaderboard under the Worker’s health. In dev, pithy seed includes a demo board with a cast of entries, so the read path is exercisable before you have written a single score of your own.