> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Leaderboards

> Recreate Steam leaderboards on XBOX GDK using event-based stats and Partner Center stat rules, with code samples comparing the two leaderboard APIs.

XBOX services leaderboards are derived from user statistics and built by queries. This is different from the leaderboards on Steam, which are driven by "scores" that are uploaded to the API. Similar to achievements, the XBOX Game Development Kit (GDK) has two APIs for statistics: event-based and title-managed. Unlike achievements, we recommend that you use event-based statistics and leaderboards over title-managed. We focus on that API in this topic. For more information about how these two APIs differ, see [Event-based vs. title-managed Stats](/services/xbox-services/player-data/stats-leaderboards/index).

Both platforms define leaderboards on their portals. The difference is that XBOX services has you first define a stat rule on Partner Center, and then build a leaderboard based on that statistic that the rule drives. Steam, on the other hand, doesn't make a distinction between the two: you create a leaderboard in their portal or programmatically do so, and then upload user scores directly to that leaderboard.

To illustrate how each of these APIs can be used compared to the Steam Leaderboard API, code examples are provided about how to create a leaderboard for furthest distance traveled (the example is from the Steamworks API documentation) on the Steam SDK and the event-based XBOX Game Development Kit (GDK) stats platform. The following sections walk through the logic.

## Creating a stat/leaderboard in the portal

### Steamworks

On the Steamworks admin portal, go to **Stats & Achievements** > **Leaderboards**. Complete the form as shown in the following screenshot (from the Steamworks documentation).

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/steam-portal-add-leaderboard.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=e8a3f4a76f8a9bdc60689571bf5a9dbb" alt="Screenshot of the form used to create a leaderboard in the Steamworks Admin Portal" width="806" height="190" data-path="images/nda/steam-porting-guide/steam-portal-add-leaderboard.png" />

### XBOX Game Development Kit (GDK) (event-based)

On Partner Center, go to your game and select **XBOX services** > **Gameplay settings**. On the navigation bar on the right pane, select **Player Stats** > **Stat Rules** and add a stat rule as **Show**, configuring it to meet your stat's needs. Because our stat updates will include a calculated value of how far the player (user) has traveled since the last update, we'll specify a measurement field for the service to get this value from.

<Note>
  There's no functional difference if you store the data to update an event in the event's Measurements or Dimensions field. The two fields exist for use with Application Insights. For more information about this, see \[Application Insights API for custom events and
</Note>

metrics]\([https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics](https://learn.microsoft.com/en-us/azure/azure-monitor/app/api-custom-events-metrics)).

The following screenshot shows the form that's used to create a stat rule in Partner Center.

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/partner-center-stat-rule.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=08b05f51f44f9f912854674ea3c189ae" alt="Screenshot of the form used to create a stat rule in Partner Center" width="1196" height="1046" data-path="images/nda/steam-porting-guide/partner-center-stat-rule.png" />

After saving, select **Leaderboard** on the top bar of the right pane and create a new leaderboard that's driven by the stat you just created as shown in the following screenshot.

<img src="https://mintcdn.com/microsoft-4404708b/5IpvKlT-jmAaVkeY/images/nda/steam-porting-guide/partner-center-leaderboard.png?fit=max&auto=format&n=5IpvKlT-jmAaVkeY&q=85&s=5ffd10e8fdb53ed640fcb59ef31e323a" alt="Screenshot of the form used to create a leaderboard in Partner Center" width="1048" height="654" data-path="images/nda/steam-porting-guide/partner-center-leaderboard.png" />

## Uploading a score

### Steamworks

On Steamworks, you need to calculate the new total value of feet traveled and send it to the API by using the `ISteamUserStats::UploaderLeaderboardScore` method.

```cpp theme={null}
int32 newFeetTraveled; // Calculate this however you want.
const int32* scoreDetails = {}; // Extra values pertaining to the score.
SteamLeaderboard_t leaderboard; // Handle for the leaderboard to upload the score to...
SteamAPICall_t result = UploadLeaderboardScore( leaderboard, ISteamUserStats::k_ELeaderboardUploadScoreMethodForceUpdate, 100, scoreDetails, int 0);
STEAM_CALLBACK(MyGameClass, OnLeaderboardScoreUploaded, LeaderboardScoreUploaded_t);
// Handle errors in the OnLeaderboardScoreUploaded callback function.
```

### XBOX Game Development Kit (GDK) (event-based)

With event-based stats in the XBOX Game Development Kit (GDK), you simply need to pass the difference in feet traveled to [`XblEventsWriteInGameEvent`](/reference/live/xsapi-c/events_c/events_c_members) because we specified that we want to increment this stat when receiving an event and have specified where to find this `diff` value in the measurements JSON string, shown as follows.

```cpp theme={null}
// Assume that this holds the feet that the user has traveled since the last update.
int diffFeetTraveled; 
std::stringstream ss;
ss << "{ \"feet\": " << diffFeetTraveled << " }";
std::string measurements = ss.str();
HRESULT hr = XblEventsWriteInGameEvent(
    m_xboxLiveContext,
    "FeetTraveled",
    "",
    measurements.c_str()
);
```

For more information, see [Writing an event to power an event-based Stat](/services/xbox-services/player-data/stats-leaderboards/index).

## Getting a global leaderboard

Global leaderboards can be fetched with the Steamworks SDK by calling `ISteamUserStats::GetLeaderboardEntries`, and then calling `ISteamUserStats::GetDownloadedLeaderboardEntry` in the `callback` function to get each entry or "row" of the leaderboard.

### Steamworks

```cpp theme={null}
class MyGameClass
{
    void OnFindLeaderboardCompleted(LeaderboardFindResult_t *callback);
    SteamAPICall_t m_getLoaderboardCall;
    SteamAPICall_t m_getEntriesCall;
};

m_getLoaderboardCall = SteamUserStats()->FindLeaderboard(leaderboardName);
STEAM_CALLBACK(MyGameClass, OnFindLeaderboardCompleted, LeaderboardFindResult_t);

// ...

void MyGameClass::OnFindLeaderboardCompleted(LeaderboardFindResult_t *callback)
{
    if (callback->bLeaderboardFound)
    {
        auto handle = callback->m_hSteamLeaderboard;
        const char *leaderboardName = SteamUserStats()->GetLeaderboardName(handle);
        m_getEntriesCall = SteamUserStats()->GetLeaderboardEntries(handle, ELeaderboardDataRequest::k_ELeaderboardDataRequestGlobal, 0, 100);
        STEAM_CALLBACK(MyGameClass, OnLeaderboardScoresDownloaded, LeaderboardScoresDownloaded_t);
    }
    else
    {
        // Leaderboard wasn't found.
    }

void MyGameClass::OnLeaderboardScoresDownloaded(LeaderboardScoresDownloaded_t *callback)
{
    int numScores = callback->m_cEntryCount;
    for (int i = 0; i < numScores; i++)
    {
        LeaderboardEntry_t entry;
        bool result = SteamUserStats()->GetDownloadedLeaderboardEntry(callback->m_hSteamLeaderboardEntries, i, &entry, nullptr, 0);
        if (!result)
        {
            // Handle error.
        }
        CSteamID userId = entry.m_steamIDUser;
        int32 rank = entry.m_nglobalRank;
        int32 score = entry.m_nScore;
        // Do something with the data. 
    }
}
```

### XBOX Game Development Kit (GDK) (event-based)

The XBOX Game Development Kit (GDK) APIs follow a similar pattern. Build a query specifying which leaderboard you want, what scope it should cover, how to sort it, which additional fields it should contain (if any), and more. After calling [`XblLeaderboardGetLeaderboardAsync`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) with your query, you can use [`XblLeaderboardGetLeaderboardResultSize`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) and [`XblLeaderboardGetLeaderboardResult`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) to put the results into your specified buffer and iterate over them as shown in the following code example.

```cpp theme={null}
auto asyncBlock = std::make_unique<XAsyncBlock>();
asyncBlock->queue = m_taskQueue;
asyncBlock->context = nullptr;
asyncBlock->callback = [](XAsyncBlock* asyncBlock)
{
    std::unique_ptr<XAsyncBlock> asyncBlockPtr{ asyncBlock }; // Take over ownership of the XAsyncBlock*
    size_t resultSize;
    std::vector<uint8_t> leaderboardBuffer;
    HRESULT hr = XblLeaderboardGetLeaderboardResultSize(asyncBlock, &resultSize);

    if (SUCCEEDED(hr))
    {
        leaderboardBuffer.resize(resultSize);
        XblLeaderboardResult* leaderboard{};

        hr = XblLeaderboardGetLeaderboardResult(asyncBlock, resultSize, leaderboardBuffer.data(), &leaderboard, nullptr);

        if (SUCCEEDED(hr))
        {
            // Use XblLeaderboardResult in result.
            for (int row = 0; row < leaderboard->rowsCount; row++)
            {
                uint64_t xuid = leaderboard->rows[row].xboxUserId;
                uint32_t rank = leaderboard->rows[row].rank;
                const char** values = leaderboard->rows[row].columnValues;
                // Do something with the data.
            }
        }
    }
};

XblLeaderboardQuery leaderboardQuery = {}; 
strcpy(&leaderboardQuery.scid[0], m_scid.c_str());
leaderboardQuery.leaderboardName = leaderboardName.c_str(); 
leaderboardQuery.xboxUserId = 0;
leaderboardQuery.order = XblLeaderboardSortOrder::Descending;
leaderboardQuery.skipResultToRank = 0;
leaderboardQuery.maxItems = 100;
leaderboardQuery.statName = "MyStatName";
leaderboardQuery.socialGroup = XblLeaderboardQueryType::None;
// See the link as follows for more options in XblLeaderboardQuery.

HRESULT hr = XblLeaderboardGetLeaderboardAsync(
    xboxLiveContext,
    leaderboardQuery,
    asyncBlock.get());
if (SUCCEEDED(hr))
{
    // The call succeeded, so release the std::unique_ptr ownership of XAsyncBlock* since the callback will take over ownership.
    // If the call fails, the std::unique_ptr will keep ownership and delete the XAsyncBlock*
    asyncBlock.release();
}
```

For more information, see the following:

* [Example code for event-based Leaderboards](/services/xbox-services/player-data/stats-leaderboards/index)
* [XblLeaderboardQuery](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members)

## Getting a social leaderboard

On Steamworks, you can use the `ELeaderboardDataRequest::k_ELeaderboardDataRequestFriends` enum to scope the leaderboard to the current user's friends.

In the XBOX Game Development Kit (GDK), this is known as a *social leaderboard*. To get a social leaderboard from XBOXBOX services, set `xboxIUserId` equal to the XBOX User ID (XUID) of the current user who is signed in to XBOX services, `leaderboardName` to `nullptr`, and the `socialGroup` as `XblSocialGroupType::People` or `XblSocialGroupType::Favorites` in your [`XblLeaderboardQuery`](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members) struct. Call `XblLeaderboardGetLeaderboardAsync` as previously shown.

For more information, see [XblSocialGroupType](/reference/live/xsapi-c/leaderboard_c/leaderboard_c_members).

## Featured Stats

In addition to the usual user stats that are used to generate Steam-like leaderboards, XBOX services has an additional concept called *Featured Stats*. Featured Stats appear on several different surfaces across the XBOX ecosystem for your game and can show the values of user's stats or leaderboards that are already generated. You can create up to 20 Featured Stats for your game.

For more information about Featured Stats and how to add them, see [Featured Stats overview](/services/xbox-services/player-data/stats-leaderboards/index) and [Portal configuration of event-based Featured Stats](/services/xbox-services/player-data/stats-leaderboards/index).

## Rate limiting

When writing events, be careful to avoid hitting the XBOX Services API (XSAPI) too often, because it may cause your game to hit its rate limit. This may cause unexpected behavior and a bad experience for your users. Note that this is an XBOX services concern that isn't an issue on Steam. Writing stat values on Steam doesn't cause an API call to the server until your game explicitly syncs them.

For events that frequently occur, try batching API calls to be fired after a certain number of events happen or to update on the server at a certain time interval.

You can debug your API calls by using the [XBOX Live Trace Analyzer (XblTraceAnalyzer.exe)](/tools/tools-services/live-trace-analyzer).

For more information about rate limiting on XBOX services, see [Fine-Grained Rate Limiting](/services/xbox-services/develop/best-practices/live-fine-grained-rate-limiting).


## Related topics

- [Tournaments & Leaderboards](/services/playfab/community/leaderboards/tournaments-leaderboards/index.md)
- [Leaderboards Meters](/services/playfab/pricing/meters/leaderboard-meters.md)
- [Quickstart on leaderboards](/services/playfab/community/leaderboards/quickstart-leaderboards.md)
- [Quota Leaderboards](/services/playfab/community/leaderboards/quota-leaderboards.md)
- [Limits on Leaderboards](/services/playfab/community/leaderboards/limits-leaderboards.md)
