# Leaderboards

**URL:** https://heroiclabs.com/docs/nakama/concepts/leaderboards/
**Summary:** Leaderboards are a great way to add a social and competitive element to any game. They're a fun way to drive competition among your players. The server supports an unlimited number of individual leaderboards with each one as a scoreboard which tracks separate records.
**Keywords:** reset leaderboards, leaderboard records, create leaderboard, submit score, list records, by score, by friends, around owner, metadata
**Categories:** nakama, leaderboards, concepts

---


# Leaderboards

Leaderboards are a great way to add a social and competitive element to any game. They're a fun way to drive competition among your players. The server supports an **unlimited** number of individual leaderboards with each one as a scoreboard which tracks separate records.

The server has no special requirement on what the score value should represent from your game. A leaderboard is created with a sort order on values. If you're using lap time or currency in records you'll want to order the results in `asc` or `desc` mode, as preferred. At creation time you must also specify the [operator](#operators) which controls how scores are submitted to the leaderboard: `best`, `set`, `incr`, or `decr`.

You can use a leaderboard to track any score you like. Some good examples are: highest points, longest survival time, fastest lap time, quickest level completion, and anything else which can be competed over!

Leaderboards are dynamic in the server because they don't need to be preconfigured like would be needed if you've used Google Play Games or Apple Game Center in the past. A leaderboard can be created via server-side code.

## Leaderboard object

Each leaderboard is a collection of records where each record is a ranked score with metadata. A leaderboard is uniquely identified by an ID.

All leaderboard configuration is immutable once created. You should delete the leaderboard and create a new one if you need to change the sort order, operator, reset schedule, or whether it is an authoritative leaderboard or not.

### Authoritative leaderboards

Creating an authoritative leaderboard means that clients will not be able to submit scores directly to the leaderboard, all [submissions](#submit-a-score) must be made via the [server runtime](../../server-framework/typescript-runtime/function-reference/#leaderboardRecordWrite).

By default leaderboards are not authoritative, meaning that clients can submit scores directly to the leaderboard. You must explicitly set the authoritative flag to `true` when creating the leaderboard, it cannot be changed later.

### Sort order

Leaderboard records are sorted based on their configured sort order: `DESC` (default) or `ASC`. The sort order is decided when a leaderboard is created and cannot be changed later.

### Operators

Configure how leaderboard record scores are set using the following `operator` values:

- `set` - The value will be set to the submitted score
- `best` - The value will only be updated if the score being submitted is higher than the existing score
- `incr` - The value will be incremented by the score being submitted to it
- `decr` - The value will be decreased by the score being submitted to it

The operator cannot be changed after the leaderboard is created.

### Reset schedules

You can assign each leaderboard an optional reset schedule. Records contained in the leaderboard will expire based on this schedule and users will be able to submit new scores for each reset cycle. At the expiry of each reset period the server triggers callbacks with the current leaderboard state. Read about about it [Leaderboards Best Practices](./best-practices/#the-lifecycle-of-a-leaderboard) and see the [Tiered Leagues](../../guides/concepts/tiered-leagues/) guide for an example use case.

Reset schedules are defined in [CRON](https://en.wikipedia.org/wiki/Cron) format when the leaderboard is created. If a leaderboard has no reset schedule set its records will never expire.

### Leaderboard records

Each leaderboard contains a list of records ordered by their scores.

All records belong to an owner. This is usually a user but other objects like a group ID or some other custom ID can be used. Each owner will only have one record per leaderboard. If a leaderboard expires each owner will be able to submit a new score which rolls over.

The score in each record can be updated as the owner progresses. Scores can be updated as often as wanted and can increase or decrease depending on the combination of leaderboard sort order and operator.

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>" \
  -H 'Authorization: Bearer <session token>'
  -d '{"record": {"score": 100}}'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "level1";
var submission = {score: 100};
var record = await client.writeLeaderboardRecord(session, leaderboardId, submission);
console.log("New record username %o and score %o", record.username, record.score);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboardId = "level1";
const long score = 100L;
var r = await client.WriteLeaderboardRecordAsync(session, leaderboardId, score);
System.Console.WriteLine("New record for '{0}' score '{1}'", r.Username, r.Score);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "level1"
let score = 100

let record = try await client.writeLeaderboardRecord(session: session, leaderboardId: leaderboardId, score: score)
print("New record for \(record.username) score \(record.score)")
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'level1';
const score = 100;

final record = await client.writeLeaderboardRecord(
  session: session,
  leaderboardName: leaderboardId,
  score: score,
);
print('New record for ${record.username} score ${record.score}');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NLeaderboardRecord& record)
{
    std::cout << "New record with score " << record.score << std::endl;
};

string leaderboardId = "level1";
int64_t score = 100;

client->writeLeaderboardRecord(session, leaderboardId, score, opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
final String leaderboard = "level1";
long score = 100L;
LeaderboardRecord r = client.writeLeaderboardRecord(session, leaderboard, score);
System.out.format("New record for %s score %s", r.getUsername(), r.getScore());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var score = 100
var record : NakamaAPI.ApiLeaderboardRecord = yield(client.write_leaderboard_record_async(session, leaderboard_id, score), "completed")
if record.is_exception():
    print("An error occurred: %s" % record)
    return
print("New record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var score = 100
var record : NakamaAPI.ApiLeaderboardRecord = await client.write_leaderboard_record_async(session, leaderboard_id, score)
if record.is_exception():
    print("An error occurred: %s" % record)
    return
print("New record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/leaderboard/<leaderboardId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>

{
  "record": {
    "score": 100
  }
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local score = 100
local subscore = 0
local metadata = json.encode({ weather_conditions = "rain" })
local operator = "best"

local result = client.write_leaderboard_record(leaderboard_id, metadata, operator, score, subscore)
if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

### Records metadata

Each record can optionally include additional data about the score or the owner when submitted. The extra fields must be JSON encoded and submitted as the metadata. 

An example use case for metadata is info about race conditions in a driving game, such as weather, which can give extra UI hints when users list scores.

```json
{
  "surface": "wet",
  "timeOfDay": "night",
  "car": "Porsche 918 Spyder"
}
```

## Create a leaderboard

A leaderboard can be created via server-side code at startup or within a [registered function](../../server-framework/introduction/#hooks). The ID given to the leaderboard is used to submit scores to it.

{{< code type="server" >}}
```lua
local id = "level1"
local authoritative = false
local sort = "desc"
local operator = "best"
local reset = "0 0 * * 1"
local metadata = {
  weather_conditions = "rain"
}
nk.leaderboard_create(id, authoritative, sort, operator, reset, metadata)
```
{{< / code >}}

{{< code type="server" >}}
```go
id := "level1"
authoritative := false
sort := "desc"
operator := "best"
reset := "0 0 * * 1"
metadata := map[string]interface{}{"weather_conditions": "rain"}

if err := nk.LeaderboardCreate(ctx, id, authoritative, sort, operator, reset, metadata); err != nil {
    // Handle error.
}
```
{{< / code >}}

{{< code type="server" >}}
```typescript
let id = '4ec4f126-3f9d-11e7-84ef-b7c182b36521';
let authoritative = false;
let sort = nkruntime.SortOrder.DESCENDING;
let operator = nkruntime.Operator.BEST;
let reset = '0 0 * * 1';
let metadata = {
  weatherConditions: 'rain',
};
try {
    nk.leaderboardCreate(id, authoritative, sort, operator, reset, metadata);
} catch(error) {
    // Handle error
}
```
{{< / code >}}

## Submit a score

A user can submit a score to a leaderboard and update it at any time **for non-authoritative leaderboards**. For authoritative leaderboards, scores can only be submitted using the [server runtime functions](../../server-framework/typescript-runtime/function-reference/#leaderboardRecordWrite).

When a score is submitted the leaderboard's pre-configured sort order and operator determine what effect the operation will have.

The `set` operator will ensure the leaderboard record always keeps the latest value, even if it is worse than the previous one.

Submitting to a leaderboard with the `best` operator ensures the record tracks the best value it has seen for that record. For a descending leaderboard this means the highest value, for an ascending one the lowest. If there is no previous value for the record, this behaves like `set`.

With the `incr` operator the new value is added to any existing score for that record. If there is no previous value for the record, this behaves like `set`.

{{< code type="client" >}}
```bash
curl -X POST "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>" \
  -H 'Authorization: Bearer <session token>'
  -d '{"score": 100}'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "level1";
var submission = {score: 100};
var record = await client.writeLeaderboardRecord(session, leaderboardId, submission);
console.log("New record username %o and score %o", record.username, record.score);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboard = "level1";
const long score = 100L;
var r = await client.WriteLeaderboardRecordAsync(session, leaderboard, score);
System.Console.WriteLine("New record for '{0}' score '{1}'", r.Username, r.Score);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "level1"
let score = 100

let record = try await client.writeLeaderboardRecord(session: session, leaderboardId: leaderboardId, score: score)
print("New record for \(record.username) score \(record.score)")
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'level1';
const score = 100;

final record = await client.writeLeaderboardRecord(
  session: session,
  leaderboardName: leaderboardId,
  score: score,
);
print('New record for ${record.username} score ${record.score}');
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](const NLeaderboardRecord& record)
{
    std::cout << "New record with score " << record.score << std::endl;
};

string leaderboardId = "level1";
int64_t score = 100;

client->writeLeaderboardRecord(session, leaderboardId, score, opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
final String leaderboard = "level1";
long score = 100L;
LeaderboardRecord r = client.writeLeaderboardRecord(session, leaderboard, score);
System.out.format("New record for %s score %d", r.getUsername(), r.getScore());
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var score = 100
var record : NakamaAPI.ApiLeaderboardRecord = yield(client.write_leaderboard_record_async(session, leaderboard_id, score), "completed")
if record.is_exception():
    print("An error occurred: %s" % record)
    return
print("New record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var score = 100
var record : NakamaAPI.ApiLeaderboardRecord = await client.write_leaderboard_record_async(session, leaderboard_id, score)
if record.is_exception():
    print("An error occurred: %s" % record)
    return
print("New record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
POST /v2/leaderboard/<leaderboardId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>

{
  "score": 100
}
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local score = 100
local operator = "best"

local result = client.write_leaderboard_record(leaderboard_id, nil, operator, score)
if result.error then
  print(result.message)
  return
end
```
{{< / code >}}

## List records

A user can list records from a leaderboard. This makes it easy to compare scores to other users and see their positions.


{{< note "important" "Getting the Rank Count" >}}
The leaderboard record list result from calling any leaderboard list function includes a `RankCount` property which provides the total number of ranked records in the specified leaderboard. This is only populated for leaderboards that are part of the rank cache (i.e. are not included in the `leaderboard.blacklist_rank_cache` [configuration property](../../getting-started/configuration/#leaderboard.blacklist_rank_cache)).
{{< /note >}}

### List by score

The standard way to list records is ordered by score based on the sort order in the leaderboard.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "level1";
var result = await client.listLeaderboardRecords(session, leaderboardId);
result.records.forEach(function(record) {
  console.log("Record username %o and score %o", record.username, record.score);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboardId = "level1";
var result = await client.ListLeaderboardRecordsAsync(session, leaderboardId);
foreach (var r in result.Records)
{
    System.Console.WriteLine("Record for '{0}' score '{1}'", r.Username, r.Score);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "level1"

let result = try await client.listLeaderboardRecords(session: session, leaderboardId: leaderboardId)

for record in result.records {
    print("Record for \(record.username) score \(record.score)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'level1';

final result = await client.listLeaderboardRecords(
  session: session,
  leaderboardName: leaderboardId,
);

for (final record in result.records) {
  print('Record for ${record.username} score ${record.score}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NLeaderboardRecordListPtr recordsList)
{
    for (auto& record : recordsList->records)
    {
        std::cout << "Record username " << record.username << " and score " << record.score << std::endl;
    }
};

string leaderboardId = "level1";

client->listLeaderboardRecords(session, leaderboardId, {}, opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
final String leaderboard = "level1";
LeaderboardRecordList records = client.listLeaderboardRecords(session, leaderboard);
for (LeaderboardRecord record : records.getRecordsList()) {
    System.out.format("Record for %s score %d", record.getUsername(), record.getScore());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_id), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var result : NakamaAPI.ApiLeaderboardRecordList = await client.list_leaderboard_records_async(session, leaderboard_id)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/leaderboard/<leaderboardId>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local result = client.list_leaderboard_records(leaderboard_id)
if result.error then
  print(result.message)
  return
end
for _,record in ipair(result.records) do
  pprint(record)
end
```
{{< / code >}}

You can fetch the next set of results with a cursor.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>?cursor=<next_cursor>" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "level1";

var result = await client.listLeaderboardRecords(session, leaderboardId);
result.records.forEach(function(record) {
  console.log("Record username %o and score %o", record.username, record.score);
});

// If there are more results get next page.
if (result.next_cursor) {
  result = await client.listLeaderboardRecords(session, leaderboardId, null, null, result.next_cursor);
  result.records.forEach(function(record) {
    console.log("Record username %o and score %o", record.username, record.score);
  });
}
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboardId = "level1";
var result = await client.ListLeaderboardRecordsAsync(session, leaderboardId);
foreach (var r in result.Records)
{
    System.Console.WriteLine("Record for '{0}' score '{1}'", r.Username, r.Score);
}
// If there are more results get next page.
if (result.NextCursor != null)
{
    var c = result.NextCursor;
    result = await client.ListLeaderboardRecordsAsync(session, leaderboardId, null, 100, c);
    foreach (var r in result.Records)
    {
        System.Console.WriteLine("Record for '{0}' score '{1}'", r.Username, r.Score);
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "level1"

var result = try await client.listLeaderboardRecords(session: session, leaderboardId: leaderboardId)

for record in result.records {
    print("Record for \(record.username) score \(record.score)")
}

// If there are more results get next page.
if !result.nextCursor.isEmpty {
    result = try await client.listLeaderboardRecords(session: session, leaderboardId: leaderboardId, limit: 100, cursor: result.nextCursor)
    for record in result.records {
        print("Record for \(record.username) score \(record.score)")
    }
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'level1';

var result = await client.listLeaderboardRecords(
  session: session,
  leaderboardName: leaderboardId,
);

for (final record in result.records) {
  print('Record for ${record.username} score ${record.score}');
}

// If there are more results get next page.
if (result.nextCursor != null) {
  result = await client.listLeaderboardRecords(
    session: session,
    leaderboardName: leaderboardId,
    limit: 100,
    cursor: result.nextCursor,
  );
  for (final record in result.records) {
    print('Record for ${record.username} score ${record.score}');
  }
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [this](NLeaderboardRecordListPtr recordsList)
{
    for (auto& record : recordsList->records)
    {
        std::cout << "Record username " << record.username << " and score " << record.score << std::endl;
    }

    if (!recordsList->nextCursor.empty())
    {
        auto successCallback = [this](NLeaderboardRecordListPtr recordsList)
        {
            for (auto& record : recordsList->records)
            {
                std::cout << "Record username " << record.username << " and score " << record.score << std::endl;
            }
        };

        string leaderboardId = "level1";

        client->listLeaderboardRecords(session, leaderboardId, {}, opt::nullopt, recordsList->nextCursor, successCallback);
    }
};

string leaderboardId = "level1";

client->listLeaderboardRecords(session,
    leaderboardId,
    {},
    opt::nullopt,
    opt::nullopt,
    successCallback
);
```
{{< / code >}}

{{< code type="client" >}}
```java
final String leaderboard = "level1";
LeaderboardRecordList records = client.listLeaderboardRecords(session, leaderboard);
for (LeaderboardRecord record : records.getRecordsList()) {
    System.out.format("Record for %s score %d", record.getUsername(), record.getScore());
}

// If there are more results get next page.
if (records.getCursor() != null) {
    var c = result.NextCursor;
    records = client.listLeaderboardRecords(session, leaderboard, null, 100, records.getNextCursor());
    for (LeaderboardRecord record : records.getRecordsList()) {
        System.out.format("Record for %s score %d", record.getUsername(), record.getScore());
    }
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_id, null, null, 100), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])

if result.next_cursor:
    result = yield(client.list_leaderboard_records_async(session, leaderboard_id, null, null, 100, result.next_cursor), "completed")
    if result.is_exception():
        print("An error occurred: %s" % result)
        return
    for r in result.records:
        var record : NakamaAPI.ApiLeaderboardRecord = r
        print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var result : NakamaAPI.ApiLeaderboardRecordList = await client.list_leaderboard_records_async(session, leaderboard_id, null, null, 100)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])

if result.next_cursor:
    result = await client.list_leaderboard_records_async(session, leaderboard_id, null, null, 100, result.next_cursor)
    if result.is_exception():
        print("An error occurred: %s" % result)
        return
    for r in result.records:
        var record : NakamaAPI.ApiLeaderboardRecord = r
        print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/leaderboard/<leaderboardId>?cursor=<next_cursor>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local cursor = nil
local owner_id = nil
local limit = 100
repeat
  local result = client.list_leaderboard_records(leaderboard_id, owner_id, limit, cursor)
  if result.error then
    print(result.message)
    return
  end
  for _,record in ipair(result.records) do
    pprint(record)
  end
until not cursor
```
{{< / code >}}

### List by friends

You can use a bunch of owner IDs to filter the records to only ones owned by those users. This can be used to retrieve only scores belonging to the user's friends.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>?owner_ids=some&owner_ids=friends" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "level1";
var ownerIds = ["some", "friends", "user ids"];
var result = await client.listLeaderboardRecords(session, leaderboardId, ownerIds);
result.records.forEach(function(record) {
  console.log("Record username %o and score %o", record.username, record.score);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboardId = "level1";
var ownerIds = new[] {"some", "friends", "user ids"};
var result = await client.ListLeaderboardRecordsAsync(session, leaderboardId, ownerIds);
foreach (var r in result.OwnerRecords)
{
    System.Console.WriteLine("Record for '{0}' score '{1}'", r.Username, r.Score);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "level1"
let ownerIds = ["some", "friends", "user ids"]

let result = try await client.listLeaderboardRecords(session: session, leaderboardId: leaderboardId, ownerIds: ownerIds)

for record in result.records {
    print("Record for \(record.username) score \(record.score)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'level1';
const ownerIds = ['some', 'friends', 'user ids'];

final result = await client.listLeaderboardRecords(
  session: session,
  leaderboardName: leaderboardId,
  ownerIds: ownerIds,
);

for (final record in result.records) {
  print('Record for ${record.username} score ${record.score}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NLeaderboardRecordListPtr recordsList)
{
    for (auto& record : recordsList->records)
    {
        std::cout << "Record username " << record.username << " and score " << record.score << std::endl;
    }
};

vector<string> ownerIds = { "some", "friends", "user ids" };
string leaderboardId = "level1";

client->listLeaderboardRecords(session, leaderboardId, ownerIds, opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String leaderboard = "level1";
String[] ownerIds = new String[] {"some", "friends", "user ids"};
LeaderboardRecordList records = await client.ListLeaderboardRecordsAsync(session, leaderboard, ownerIds);
for (LeaderboardRecord record : records.getRecordsList()) {
    System.out.format("Record for %s score %d", record.getUsername(), record.getScore());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var owner_ids = ["some", "friend", "user id"]
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_id, owner_ids), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var owner_ids = ["some", "friend", "user id"]
var result : NakamaAPI.ApiLeaderboardRecordList = await client.list_leaderboard_records_async(session, leaderboard_id, owner_ids)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/leaderboard/<leaderboardId>?owner_ids=some&owner_ids=friends
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local owner_ids = { "some", "friend", "user id" }
local result = client.list_leaderboard_records(leaderboard_id, owner_ids)
if result.error then
  print(result.message)
  return
end
for _,record in ipair(result.records) do
  pprint(record)
end
```
{{< / code >}}

### List expired records

Expired records after each leaderboard [reset](#reset-schedules) are removed from the leaderboard rankings but they are not deleted. A user can list expired records for their desired time period.

For example, to list records expired in the past week:

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/leaderboard/<leaderboardId>?overrideExpiry=604800" \
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var leaderboardId = "<leaderboardId>";
var overrideExpiry = 604800;
var result = await client.listLeaderboardRecords(session, leaderboardId, overrideExpiry);
result.records.forEach(function(record) {
  console.log("Record username %o and score %o expired on %o", record.username, record.score, record.expiryTime);
});
```
{{< / code >}}

{{< code type="client" >}}
```csharp
const string leaderboardId = "<leaderboardId>";
const int64 overrideExpiry = 604800;
var result = await client.ListLeaderboardRecordsAsync(session, leaderboardId, overrideExpiry);
foreach (var r in result.Records)
{
    System.Console.WriteLine("Record for '{0}' and score '{1}' expired on '{2}'", r.Username, r.Score, r.ExpiryTime);
}
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "<leaderboardId>"
let expiry = 604800 // 7 days

let result = try await client.listLeaderboardRecords(session: session, leaderboardId: leaderboardId, expiry: expiry)

for record in result.records {
    print("Record username \(record.username) and score \(record.score) expired on \(record.expiryTime)")
}
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = '<leaderboardId>';
final expiry = DateTime.now().add(const Duration(days: 7));

final result = await client.listLeaderboardRecords(
  session: session,
  leaderboardName: leaderboardId,
  expiry: expiry,
);

for (final record in result.records) {
  print('Record for ${record.username} and score ${record.score} expired on ${record.expiryTime}');
}
```
{{< / code >}}

{{< code type="client" >}}
```cpp
auto successCallback = [](NLeaderboardRecordListPtr recordsList)
{
    for (auto& record : recordsList->records)
    {
        std::cout << "Record username " << record.username << " and score " << record.score << " expired on " << record.expiryTime << std::endl;
    }
};

string leaderboardId = "<leaderboardId>";
int overrideExpiry = 604800;

client->listLeaderboardRecords(session, leaderboardId, overrideExpiry, {}, opt::nullopt, opt::nullopt, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
final String leaderboard = "<leaderboardId>";
final Long overrideExpiry = 604800;
LeaderboardRecordList records = client.listLeaderboardRecords(session, leaderboard, overrideExpiry).get();
for (LeaderboardRecord record : records.getRecordsList()) {
    System.out.format("Record for %s score %d", record.getUsername(), record.getScore());
}
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "<leaderboardId>"
var override_expiry = 604800
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_async(session, leaderboard_id, override_expiry), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s expired on %s" % [record.username, record.score, record.expiry_time])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "<leaderboardId>"
var override_expiry = 604800
var result : NakamaAPI.ApiLeaderboardRecordList = await client.list_leaderboard_records_async(session, leaderboard_id, override_expiry)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s expired on %s" % [record.username, record.score, record.expiry_time])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/leaderboard/<leaderboardId>?overrideexpiry=604800
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "<leaderboardId>"
local override_expiry = 604800
local result = client.list_leaderboard_records(leaderboard_id, owner_ids, limit, cursor, override_expiry)
if result.error then
  print(result.message)
  return
end
for _,record in ipair(result.records) do
  pprint(record)
end
```
{{< / code >}}

### List leaderboard records around owner

Fetch the list of leaderboard records around the owner.

{{< code type="client" >}}
```bash
curl -X GET "http://127.0.0.1:7350/v2/leaderboard/<leaderboard_id>/owner/<owner_id>?limit=<limit>"
  -H 'Authorization: Bearer <session token>'
```
{{< / code >}}

{{< code type="client" >}}
```javascript
var id = "someid";
var ownerId = "some user ID";
var limit = 100;
var result = await client.listLeaderboardRecordsAroundOwner(session, id, ownerId, limit);
```
{{< / code >}}

{{< code type="client" >}}
```csharp
var leaderboardId = "someid";
var ownerId = session.UserId;
var limit = 100;
var result = await client.ListLeaderboardRecordsAroundOwnerAsync(session, leaderboardId, ownerId, limit);
```
{{< / code >}}

{{< code type="client" >}}
```swift
let leaderboardId = "someid"
let ownerId = session.userId
let limit = 100

var result = try await client.listLeaderboardRecordsAroundOwner(session: session, leaderboardId: leaderboardId, ownerId: ownerId, limit: limit)
```
{{< / code >}}

{{< code type="client" >}}
```dart
const leaderboardId = 'someid';
final ownerId = session.userId;

final result = await client.listLeaderboardRecordsAroundOwner(
  session: session,
  leaderboardName: leaderboardId,
  ownerId: ownerId,
  limit: 100,
);
```
{{< / code >}}

{{< code type="client" >}}
```cpp
string leaderboardId = "level1";
string ownerId = "some user ID";
int32_t limit = 100;
client->listLeaderboardRecordsAroundOwner(session, leaderboardId, ownerId, limit, successCallback);
```
{{< / code >}}

{{< code type="client" >}}
```java
String id = "someid";
String ownerId = session.getUserId();
int limit = 100;
LeaderboardRecordList records = client.listLeaderboardRecordsAroundOwner(session, id, ownerId, limit).get();
```
{{< / code >}}

{{< code type="client" framework="godot3" >}}
```gdscript
var leaderboard_id = "level1"
var owner_id = "user id"
var result : NakamaAPI.ApiLeaderboardRecordList = yield(client.list_leaderboard_records_around_owner_async(session, leaderboard_id, owner_id), "completed")
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" framework="godot4" >}}
```gdscript
var leaderboard_id = "level1"
var owner_id = "user id"
var result : NakamaAPI.ApiLeaderboardRecordList = await client.list_leaderboard_records_around_owner_async(session, leaderboard_id, owner_id)
if result.is_exception():
    print("An error occurred: %s" % result)
    return
for r in result.records:
    var record : NakamaAPI.ApiLeaderboardRecord = r
    print("Record username %s and score %s" % [record.username, record.score])
```
{{< / code >}}

{{< code type="client" >}}
```shell
GET /v2/leaderboard/<leaderboard_id>/owner/<owner_id>?limit=<limit>
Host: 127.0.0.1:7350
Accept: application/json
Content-Type: application/json
Authorization: Bearer <session token>
```
{{< / code >}}

{{< code type="client" framework="defold" >}}
```lua
local leaderboard_id = "level1"
local owner_id = "user id"
local result = client.list_leaderboard_records_around_owner(leaderboard_id, owner_id)
if result.error then
  print(result.message)
  return
end
for _,record in ipair(result.records) do
  pprint(record)
end
```
{{< / code >}}

## Additional Information

- [Nakama Leaderboards sample project](../../../sample-projects/unity/nakama-leaderboards)
- [Mage Mayhem sample project](../../../sample-projects/games/mage-mayhem)