Search documentation

Find pages, sections, and content across all docs.

WavedashDocs

Leaderboards

Create competitive scoreboards, submit scores, and query player rankings

Leaderboards let players compete by ranking scores on shared scoreboards. Each leaderboard has a name you pick (e.g. "speedrun-times"), a sort direction, and an update policy.

Name vs. ID. Only getLeaderboard(name) and getOrCreateLeaderboard(name, ...) take the leaderboard's name. Every other call — uploadLeaderboardScore, listLeaderboardEntries, getMyLeaderboardEntries, etc. — takes the ID returned from those lookups (response.data.id)

Getting or creating a leaderboard

Call this once per leaderboard (for example during startup) and reuse the returned ID for every other leaderboard call.

func setup_leaderboard():
    var leaderboard = await WavedashSDK.get_or_create_leaderboard(
        "speedrun-times",
        WavedashConstants.LEADERBOARD_SORT_ASCENDING,
        WavedashConstants.LEADERBOARD_DISPLAY_TYPE_TIME_MILLISECONDS
    )
    var leaderboard_id = leaderboard.data.id if leaderboard.success else ""
    print("Leaderboard ID: ", leaderboard_id)
var leaderboard = await Wavedash.SDK.GetOrCreateLeaderboard(
    "speedrun-times",
    sortMethod: WavedashConstants.LeaderboardSortMethod.ASCENDING,
    displayType: WavedashConstants.LeaderboardDisplayType.TIME_MILLISECONDS
);
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;
Debug.Log($"Leaderboard ID: {leaderboardId}");
const leaderboard = await Wavedash.getOrCreateLeaderboard(
  "speedrun-times",
  Wavedash.LeaderboardSortOrder.ASC,
  Wavedash.LeaderboardDisplayType.TIME_MILLISECONDS
);
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

If the leaderboard already exists and you just need its ID, use getLeaderboard(name) instead — it fails if the leaderboard doesn't exist yet.

Godot signal alternative. If you prefer signal-based flow over await, every leaderboard call also emits a signal when its response arrives:

func _ready():
    WavedashSDK.got_leaderboard.connect(_on_got_leaderboard)
    WavedashSDK.got_leaderboard_entries.connect(_on_got_entries)
    WavedashSDK.posted_leaderboard_score.connect(_on_posted_score)

func _on_got_leaderboard(response):
    print("ID: ", response.data.id if response.success else "?")

func _on_got_entries(response):
    if response.success:
        for entry in response.data:
            print(entry.globalRank, ": ", entry.score)

func _on_posted_score(response):
    if response.success:
        print("Rank: ", response.data.globalRank)

Sort order

ValueConstantDescriptionBest for
0ASCLower scores rank higherTime trials, golf
1DESCHigher scores rank higherPoints, high scores

In JavaScript use Wavedash.LeaderboardSortOrder.ASC / DESC. In Godot use WavedashConstants.LEADERBOARD_SORT_ASCENDING / LEADERBOARD_SORT_DESCENDING. In Unity use WavedashConstants.LeaderboardSortMethod.ASCENDING / DESCENDING.

Display type

ValueConstantDescription
0NUMERICDisplay as a number
1TIME_SECONDSDisplay as time in seconds
2TIME_MILLISECONDSDisplay as time with milliseconds
3TIME_GAME_TICKSDisplay as game ticks (assumes 60fps)

In JavaScript use Wavedash.LeaderboardDisplayType.NUMERIC / TIME_SECONDS / TIME_MILLISECONDS / TIME_GAME_TICKS.

Submitting a score

Resolve the name to an ID first, then pass that ID to uploadLeaderboardScore.

func submit_score(score: int):
    var leaderboard = await WavedashSDK.get_leaderboard("speedrun-times")
    if not leaderboard.success:
        return
    var leaderboard_id = leaderboard.data.id

    var result = await WavedashSDK.post_leaderboard_score(leaderboard_id, score, true)
    if result.success:
        print("Rank: ", result.data.globalRank)
var leaderboard = await Wavedash.SDK.GetLeaderboard("speedrun-times");
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;

var result = await Wavedash.SDK.UploadLeaderboardScore(
    leaderboardId, score, keepBest: true
);
if (result != null)
    Debug.Log($"Rank: {result["globalRank"]}");
const leaderboard = await Wavedash.getLeaderboard("speedrun-times");
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

const response = await Wavedash.uploadLeaderboardScore(
  leaderboardId, 1500, true
);
if (response.success) {
  console.log(`Rank: ${response.data.globalRank}`);
}
ParameterTypeRequiredDescription
leaderboardIdId<"leaderboards">YesThe leaderboard's ID (from getLeaderboard / getOrCreateLeaderboard)
scorenumberYesThe score to submit
keepBestbooleanYesIf true, only updates if score is better
ugcIdId<"userGeneratedContent">NoOptional UGC attachment (replay, screenshot)

Use keepBest: true for competitive leaderboards. The SDK handles personal best tracking automatically.

Fetching scores

Top scores

func get_top_scores():
    var leaderboard = await WavedashSDK.get_leaderboard("speedrun-times")
    if not leaderboard.success:
        return
    var leaderboard_id = leaderboard.data.id

    var response = await WavedashSDK.get_leaderboard_entries(leaderboard_id, 0, 10, false)
    if response.success:
        for entry in response.data:
            print("#", entry.globalRank, " ", entry.username, ": ", entry.score)
var leaderboard = await Wavedash.SDK.GetLeaderboard("speedrun-times");
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;

var entries = await Wavedash.SDK.ListLeaderboardEntries(leaderboardId, 0, 10);
foreach (var entry in entries)
    Debug.Log($"#{entry["globalRank"]} {entry["username"]}: {entry["score"]}");
const leaderboard = await Wavedash.getLeaderboard("speedrun-times");
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

const response = await Wavedash.listLeaderboardEntries(
  leaderboardId, 0, 10, false
);
if (response.success) {
  response.data.forEach(entry => {
    console.log(`#${entry.globalRank} ${entry.username}: ${entry.score}`);
  });
}

Nearby scores

func get_scores_around_me():
    var leaderboard = await WavedashSDK.get_leaderboard("speedrun-times")
    if not leaderboard.success:
        return
    var leaderboard_id = leaderboard.data.id

    var nearby = await WavedashSDK.get_leaderboard_entries_around_player(leaderboard_id, 5, 5, false)
var leaderboard = await Wavedash.SDK.GetLeaderboard("speedrun-times");
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;

var nearby = await Wavedash.SDK.ListLeaderboardEntriesAroundUser(
    leaderboardId, countAhead: 5, countBehind: 5
);
const leaderboard = await Wavedash.getLeaderboard("speedrun-times");
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

const nearby = await Wavedash.listLeaderboardEntriesAroundUser(
  leaderboardId, 5, 5, false
);

Player's own entry

func get_my_entry():
    var leaderboard = await WavedashSDK.get_leaderboard("speedrun-times")
    if not leaderboard.success:
        return
    var leaderboard_id = leaderboard.data.id

    var entries = await WavedashSDK.get_my_leaderboard_entries(leaderboard_id)
var leaderboard = await Wavedash.SDK.GetLeaderboard("speedrun-times");
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;

var entries = await Wavedash.SDK.GetMyLeaderboardEntries(leaderboardId);
const leaderboard = await Wavedash.getLeaderboard("speedrun-times");
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

const response = await Wavedash.getMyLeaderboardEntries(leaderboardId);

Entry count

getLeaderboardEntryCount is a synchronous accessor that returns the cached total from the last fetch — but it still takes the leaderboard ID, not the name, so you must resolve the name first.

func get_count():
    var leaderboard = await WavedashSDK.get_leaderboard("speedrun-times")
    if not leaderboard.success:
        return
    var leaderboard_id = leaderboard.data.id

    var count = WavedashSDK.get_leaderboard_entry_count(leaderboard_id)
var leaderboard = await Wavedash.SDK.GetLeaderboard("speedrun-times");
string leaderboardId = leaderboard != null ? (string)leaderboard["id"] : null;

var count = Wavedash.SDK.GetLeaderboardEntryCount(leaderboardId);
const leaderboard = await Wavedash.getLeaderboard("speedrun-times");
const leaderboardId = leaderboard.success ? leaderboard.data.id : null;

const count = Wavedash.getLeaderboardEntryCount(leaderboardId);

Returns a cached value from the last query. Returns -1 if the leaderboard has not been queried yet.

Attaching replays or screenshots

Pass a ugcId from UGC into the score upload call. Note that uploadLeaderboardScore takes lb.data.id (the ID), not the name:

const lb = await Wavedash.getOrCreateLeaderboard(
  "speedrun-times",
  Wavedash.LeaderboardSortOrder.ASC,
  Wavedash.LeaderboardDisplayType.TIME_MILLISECONDS
);
const ugc = await Wavedash.createUGCItem(
  Wavedash.UGCType.GAME_MANAGED,
  "Replay",
  "replay of level 1",
  Wavedash.UGCVisibility.PUBLIC,
  "replays/run.dat"
);
if (lb.success && ugc.success) {
  await Wavedash.uploadLeaderboardScore(lb.data.id, 12345, true, ugc.data);
}

Return types

Leaderboard

Returned from getLeaderboard and getOrCreateLeaderboard. Use id for every subsequent leaderboard call.

interface Leaderboard {
  id: Id<"leaderboards">;   // pass this to uploadLeaderboardScore, listLeaderboardEntries, etc.
  name: string;
  totalEntries: number;
  created?: boolean;        // only set by getOrCreateLeaderboard — true if newly created
}

LeaderboardEntry

An entry in the list returned by listLeaderboardEntries, listLeaderboardEntriesAroundUser, and getMyLeaderboardEntries.

interface LeaderboardEntry {
  userId: Id<"users">;
  username: string;
  userAvatarUrl?: string;
  score: number;
  globalRank: number;
  timestamp: number;
  metadata?: ArrayBuffer;
  ugcId?: Id<"userGeneratedContent">;
}

UpsertedLeaderboardEntry

Returned from uploadLeaderboardScore. Note that this is a different shape from LeaderboardEntry — it reports what changed on upload rather than the full entry.

interface UpsertedLeaderboardEntry {
  entryId: Id<"leaderboardEntries">;
  score: number;
  scoreChanged: boolean; // false if keepBest was true and the existing score was better
  globalRank: number;
  userId: Id<"users">;
  username: string;
  userAvatarUrl?: string;
}