Skip to main content
Leaderboards in s&box are stat aggregations ordered by value. You get a leaderboard from a stat name, apply any filters you need, call Refresh(), and iterate over the results.
By default, leaderboards aggregate using the sum of all stat submissions and sort in descending order.

Basic example

Fetch a global leaderboard showing who has killed the most zombies:
var board = Sandbox.Services.Leaderboards.GetFromStat( "facepunch.ss1", "zombies_killed" );
await board.Refresh();

foreach ( var entry in board.Entries )
{
    Log.Info( $"{entry.Rank} - {entry.DisplayName} - {entry.Value}" );
}
GetFromStat takes the package identifier and the stat name. The stat must already be recorded via Sandbox.Services.Stats for entries to appear.

Filtering by country

Restrict entries to a specific country using SetCountryCode:
var board = Sandbox.Services.Leaderboards.GetFromStat( "facepunch.ss1", "zombies_killed" );
board.SetCountryCode( "gb" );
await board.Refresh();

foreach ( var entry in board.Entries )
{
    Log.Info( $"{entry.Rank} - {entry.DisplayName} - {entry.Value} [{entry.CountryCode}]" );
}
Pass "auto" as the country code to use the current player’s location automatically.

Filtering by date

Use date filters to create monthly, weekly, or daily leaderboards:
var board = Sandbox.Services.Leaderboards.GetFromStat( "facepunch.ss1", "zombies_killed" );
board.FilterByMonth();
board.SetDatePeriod( new System.DateTime( 2024, 8, 1 ) );
await board.Refresh();

foreach ( var entry in board.Entries )
{
    Log.Info( $"{entry.Rank} - {entry.DisplayName} - {entry.Value} [{entry.CountryCode}]" );
}
If you call a date filter method without calling SetDatePeriod, the current date is used automatically.
Available date filter methods: FilterByMonth(), FilterByWeek(), FilterByDay().

Centering on a player

Instead of always showing the top N entries, center the results around a specific player to show their rank and the players near them:
var board = Sandbox.Services.Leaderboards.GetFromStat( "facepunch.ss1", "zombies_killed" );
board.CenterOnSteamId( 76561197960279927 );
await board.Refresh();

foreach ( var entry in board.Entries )
{
    Log.Info( $"{entry.Rank} - {entry.DisplayName} - {entry.Value}" );
}
Call .CenterOnMe() instead of CenterOnSteamId to center on the local player automatically.

Aggregation and sorting

By default, a leaderboard sums all stat submissions per player and orders by the highest total. You can change both the aggregation mode and sort direction independently. This example builds a fastest-win-time leaderboard — selecting each player’s minimum elapsed time and sorting ascending:
var board = Sandbox.Services.Leaderboards.GetFromStat( "facepunch.ss1", "victory_elapsed_time" );

board.SetAggregationMin(); // select the lowest value from each player
board.SetSortAscending();  // order by the lowest value first
board.FilterByMonth();     // only show results from this month
board.CenterOnMe();        // offset so the local player is in the middle
board.MaxEntries = 100;

await board.Refresh();

foreach ( var entry in board.Entries )
{
    Log.Info( $"{entry.Rank} - {entry.DisplayName} - {entry.Value} [{entry.Timestamp}]" );
}
You can aggregate by sum, min, max, avg, or last.
entry.Timestamp holds the time of the individual stat submission that was selected by the aggregation (for example, the timestamp of the fastest run, not the most recent run).

Entry fields

Each item in board.Entries exposes:
FieldTypeDescription
RankintPosition on the leaderboard (1-indexed).
DisplayNamestringThe player’s display name.
ValuedoubleThe aggregated stat value.
CountryCodestringThe country where the stat was recorded.
TimestampDateTimeWhen the selected stat submission was recorded.

Recording stats

Leaderboards read from stats recorded with Sandbox.Services.Stats. Record stats during gameplay:
public void OnZombieKilled()
{
    Sandbox.Services.Stats.Increment( "zombies-killed", 1 );
}

public void OnGameFinished()
{
    Sandbox.Services.Stats.Increment( "wins", 1 );
    Sandbox.Services.Stats.SetValue( "win-time", SecondsTaken );
}
You can call the stats API as often as needed. The platform batches submissions and sends them when ready.
You can also read stats directly without going through leaderboards:
// Global totals
var global = Sandbox.Services.Stats.Global.Get( "zombies_killed" );
Log.Info( $"{global.Sum} zombies killed by {global.Players} players" );

// Local player totals
var mine = Sandbox.Services.Stats.LocalPlayer.Get( "zombies_killed" );
Log.Info( $"You have killed {mine.Sum} zombies" );

// Specific player stats
var stats = Sandbox.Services.Stats.GetPlayerStats( "facepunch.ss1", 76561197960279927 );
await stats.Refresh();
var theirZombies = stats.Get( "zombies_killed" );
Log.Info( $"Garry has killed {theirZombies.Sum} zombies" );