Highscores & leaderboards

← All Community Games topics

DavidnetSDK.applyHighscore(score, options?)

Submits a score. The server only ever keeps the HIGHEST score per player and globally, so it is safe to call this every time the player's score might be a new best — on every game over, without checking first whether it's actually better. score must be a non-negative integer.

Resolves to: { score, playerHighscore, globalHighscore, isNewPersonalBest, isNewGlobalBest }

highscore-basic.js
javascript
async function onGameOver(finalScore) {
  try {
    const result = await window.DavidnetSDK.applyHighscore(finalScore);

    if (result.isNewGlobalBest) {
      showMessage("New world record!");
    } else if (result.isNewPersonalBest) {
      showMessage("New personal best: " + result.playerHighscore);
    } else {
      showMessage("Score: " + result.score + " (best: " + result.playerHighscore + ")");
    }
  } catch (e) {
    console.warn("Could not submit score", e);
  }
}

Multiple leaderboards (categories)

A game can have MULTIPLE leaderboards: pass { category: "time-attack" } (letters, numbers, - / _ , up to 50 chars) to submit to a leaderboard other than the default one. Omit it entirely and you get the classic one-leaderboard-per-game behavior — this is fully backwards compatible, every game uploaded before categories existed keeps working unchanged.

highscore-categories.js
javascript
// A racing game with one leaderboard per track.
async function onRaceFinished(trackId, timeMs) {
  try {
    const result = await window.DavidnetSDK.applyHighscore(timeMs, { category: trackId });
    if (result.isNewGlobalBest) showMessage("New track record on " + trackId + "!");
  } catch (e) {
    console.warn("Could not submit lap time", e);
  }
}

// A game with a "normal" and a "hardcore" mode, each with its own leaderboard.
async function onGameOver(finalScore, isHardcore) {
  const category = isHardcore ? "hardcore" : "default";
  try {
    await window.DavidnetSDK.applyHighscore(finalScore, { category });
  } catch (e) {
    console.warn("Could not submit score", e);
  }
}

DavidnetSDK.getHighscores(options?)

Pass { category: "time-attack" } to read a non-default leaderboard — same rule as applyHighscore . Resolves to: { playerHighscore, globalHighscore, leaderboard } — leaderboard is the top 10: [{ rank, userId, username, displayName, avatarUrl, score }, ...] . globalHighscore is that same shape for the #1 entry, or null if nobody has scored yet.

highscore-read.js
javascript
async function showLeaderboardScreen() {
  const { leaderboard, playerHighscore, globalHighscore } = await window.DavidnetSDK.getHighscores();

  renderLeaderboardRows(leaderboard);
  renderYourBest(playerHighscore); // null if you haven't scored yet

  if (globalHighscore) {
    renderTopPlayer(globalHighscore.displayName, globalHighscore.score);
  }
}

// Reading a non-default leaderboard, e.g. for a "time attack" tab.
async function showTimeAttackLeaderboard() {
  const { leaderboard } = await window.DavidnetSDK.getHighscores({ category: "time-attack" });
  renderLeaderboardRows(leaderboard);
}

A live leaderboard widget (polling)

If you show a leaderboard on-screen during gameplay and want it to update periodically, poll on an interval of a few seconds — NOT every frame. Check your rate limit budget before firing the next poll, so a busy moment elsewhere in your game doesn't tip you over.

highscore-live-widget.js
javascript
let pollTimer = null;

function startLiveLeaderboard() {
  pollTimer = setInterval(async () => {
    const status = window.DavidnetSDK.getRateLimitStatus();
    if (status && status.remaining < 5) return; // skip this tick, budget is low

    try {
      const { leaderboard } = await window.DavidnetSDK.getHighscores();
      renderLeaderboardRows(leaderboard);
    } catch (e) {
      console.warn("Leaderboard refresh failed", e);
    }
  }, 5000); // every 5 seconds is plenty for a leaderboard
}

function stopLiveLeaderboard() {
  clearInterval(pollTimer);
}

Flagged scores

A score far above the current leaderboard top is automatically flagged for review rather than immediately shown on the public leaderboard — this is anti-cheat, not something your game needs to handle specially. The call still succeeds and playerHighscore still reflects it for that player; it's simply excluded from everyone else's view of the leaderboard until the game's creator reviews it.