Leaderboard Providers
Register custom leaderboards, ranking algorithms, and unit formatters.
Modules can declare custom leaderboard providers that automatically integrate with Discord's /leaderboard slash command and the web dashboard's Leaderboards view.
Leaderboard providers reside in resources/leaderboards/ and load from every *.js file located directly in that directory.
Base Class Definition
import { Leaderboards } from "#medusa/modules";
export class ActivityLeaderboard extends Leaderboards {
constructor(medusa) {
super(medusa, {
name: "activity_score",
label: "Activity Score",
group: "Community",
format: "number",
emoji: "⭐",
unit: "Points",
singular: "Point",
order: 1
});
}
async list(guild) {
const model = this.medusa.modules.models.get(this.module, "user_scores");
if (!model) return [];
const records = await model.list({ guildId: guild.id });
return records
.sort((a, b) => b.score - a.score)
.slice(0, 100)
.map(record => ({
userId: String(record.userId),
value: record.score,
sub: `Level ${record.level} • Streak: ${record.streak} days`,
stats: [
{ label: "Level", value: record.level },
{ label: "Streak", value: `${record.streak}d` }
]
}));
}
}Constructor Options
The configuration object passed to super(medusa, meta) accepts the following properties:
| Property | Type | Default | Description |
|---|---|---|---|
name | string | Required | Unique leaderboard identifier. |
label | string | name | Display title shown in menus, embeds, and dashboard tables. |
group | string | "General" | Categorization group used to organize multiple leaderboards. |
format | string | "number" | Value formatting style: "number", "currency", or "duration". |
emoji | string | "" | Emoji prefix displayed next to values. |
unit | string | "" | Plural unit name appended to values (e.g. "Credits", "Messages"). |
singular | string | unit | Singular unit name used when value is 1 (e.g. "Credit", "Message"). |
order | number | 0 | Sort priority among leaderboards within the same group. |
Supported Formatting Modes
The format property determines how Medusa renders values in Discord embeds and dashboard widgets:
| Format Mode | Sample Output | Formatting Rules |
|---|---|---|
"number" | 1,250 Points | Localized integer formatting with unit suffix. |
"currency" | $ 5,000 Coins | Prepends emoji or currency symbol and appends unit label. |
"duration" | 4h 12m | Converts numeric seconds into human-readable hours and minutes. |
The list(guild) Method
Every provider must implement async list(guild) which receives the active Discord.js Guild instance and returns an array of ranked participant objects.
Return Item Schema
| Property | Type | Required | Description |
|---|---|---|---|
userId | string | Yes | Discord snowflake ID of the member. Used to resolve usernames and avatars. |
value | number | Yes | Raw numeric value used for ranking calculations and sorting. |
display | string | No | Optional pre-formatted text override replacing automatic numeric formatting. |
sub | string | No | Subtitle text displayed below the user's name in list items. |
stats | Array<object> | No | Array of supplementary metric objects containing label and value (string or number) rendered as badges. |
Leaderboard Manager Methods
The root this.medusa.leaderboards service provides methods used across Discord commands and web views:
const allTypes = this.medusa.leaderboards.types();
const rows = await this.medusa.leaderboards.list("activity_score", guild);
const page = this.medusa.leaderboards.paginate(rows, 0, 10);
const position = this.medusa.leaderboards.position(rows, interaction.user.id);
