You want a weekly leaderboard. Then someone asks for a monthly one. Then marketing wants a board that runs for the duration of an event that starts on a Thursday and ends on a Tuesday.
If your leaderboard product offers daily | weekly | monthly | alltime, that third request is a rewrite.
pithy add leaderboard makes a board’s window a CRON expression. Daily, weekly, calendar-month and calendar-year are the ones you will use most, and they are not special cases — they are just the CRON you would have written.
Boards are config. Adding one is a config change, not a migration.
Typos fail on deploy, not at 3am
Config that contains an expression is config that can contain a broken expression, and the worst time to discover one is when the first submission of a new window arrives.
So every CRON is validated at assembly, when the capability is composed. A typo fails your deploy. The comment in the source says it more plainly than I would:
Parses the board set, and validates every CRON at assembly — a typo fails on deploy, not on the first submission at 3am.
What happens when a window closes
This is the part worth choosing a tool over.
A window ends. The standings for that week or that month are now a historical fact — and historical facts about your players are exactly the data you want later, for a season recap, a “best of” screen, an anti-cheat investigation, or a churn analysis of who stopped competing.
Hosted leaderboard services generally treat closed windows as temporary. Retained versions are a metered resource, expiry is measured in weeks, and the documentation is often explicit that the service is not an archival store. That is a reasonable position for someone else’s infrastructure to take.
Here, a closed window is rows in your own D1 database. It lives for exactly as long as you say, and the default is to keep everything — because storage is cheap and your history is not reproducible.
More usefully: it is plain SQL you can join against your own tables. Standings joined to your users, to your purchases, to your session data. Not an export, not a webhook, not a reporting API with its own query language. A join.
Writes are server-authoritative by default
A leaderboard is the single most attractive thing in your game to lie to.
So submissions are server-authoritative unless you deliberately choose otherwise. The score your Worker records is the one your Worker computed or verified — not one a client asserted and you believed.
That default costs you a little convenience during prototyping and saves you the entire class of problem where the top of your board is populated by people who read your API.
Absent auth denies, rather than opens
There is a design detail here I want to point out, because it is the kind of thing that is invisible when right and catastrophic when wrong.
Leaderboard declares no dependency on the auth capability. It does not import it. And yet every route requires a signed-in user.
It works because auth is a seam rather than a peer. Routes read the auth context through core, the seam every capability already shares. If you have not composed auth, that context resolves to nobody — and a route with no caller denies.
The alternative implementation, where a missing dependency means the check is skipped, produces an open leaderboard API on any project that composed leaderboard and forgot auth. The failure mode here is a locked door rather than an open one, and it needed no dependency edge to achieve.
Live boards, if you want them
There is a live option that adds a WebSocket push layer so clients see standings move without polling.
It is worth being precise about what it is: a latency play over D1, not a different store. The durable answer is still rows in your database. Live is how a client hears about a change sooner, not where the change lives. If the socket drops, the data is still correct.
What it does not do
It does not do matchmaking or skill rating. A leaderboard ranks results; deciding who plays whom is matchmaking, and what a result says about a player’s ability is rating. Different problems, different math.
It does not detect cheating. Server-authoritative writes mean a client cannot simply assert a score, which removes the laziest attack. Deciding whether a legitimately-submitted score is plausible is domain logic only you can write.
And it will not tell you which board a player should see. Boards are yours to arrange.
Why config beats a schema
The thing I keep coming back to on this one is that boards being config makes experimentation cheap.
A seasonal board for an event that runs eleven days is a line. A regional board is a line. Discovering the weekly board should have been resetting on Monday rather than Sunday is a one-character change and a deploy, not a data migration.
That is the difference between a leaderboard you tune and one you inherited.