Skip to content

Leaderboard

Get user rankings based on activity in your Discord forum.

Get Leaderboard

GET /api/leaderboard/:serverId

Parameters

ParameterTypeDescription
serverIdstringDiscord server ID

Query Parameters

ParameterTypeDefaultDescription
typeenummessagesmessages, threads, reactions
limitnumber10Results (1-50)
excludeBotsbooleanfalseExclude bot users
botsOnlybooleanfalseOnly bot users

Response

{
"serverId": "123456789",
"serverName": "My Server",
"type": "messages",
"filters": {
"excludeBots": false,
"botsOnly": false
},
"leaderboard": [
{
"rank": 1,
"userId": "user001",
"username": "super_helper",
"globalName": "Super Helper",
"avatar": "abc123",
"isBot": false,
"count": 523
},
{
"rank": 2,
"userId": "user002",
"username": "active_dev",
"globalName": "Active Developer",
"avatar": "def456",
"isBot": false,
"count": 412
},
{
"rank": 3,
"userId": "bot001",
"username": "ModBot",
"globalName": null,
"avatar": "ghi789",
"isBot": true,
"count": 350
}
],
"cachedAt": "2024-01-15T12:00:00.000Z",
"expiresAt": "2024-01-15T12:02:00.000Z"
}

Response Fields

FieldTypeDescription
typestringRanking type
filtersobjectApplied filters
leaderboardarrayRanked users
cachedAtstringCache timestamp
expiresAtstringCache expiration

Leaderboard Entry Fields

FieldTypeDescription
ranknumberPosition (1-based)
userIdstringDiscord user ID
usernamestringUsername
globalNamestring | nullDisplay name
avatarstring | nullAvatar hash
isBotbooleanWhether user is a bot
countnumberMetric count

Ranking Types

Messages

Total messages sent in forum threads:

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?type=messages"

Threads

Total threads created:

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?type=threads"

Reactions

Total reactions received on messages:

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?type=reactions"

Filtering

Exclude Bots

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?excludeBots=true"

Bots Only

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?botsOnly=true"

Examples

Terminal window
curl "http://localhost:3000/api/leaderboard/123456789?type=messages&limit=10"

Implementation Examples

Leaderboard Component

function Leaderboard({ serverId }) {
const [type, setType] = useState('messages');
const [excludeBots, setExcludeBots] = useState(false);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchLeaderboard() {
setLoading(true);
const params = new URLSearchParams({
type,
limit: '20',
excludeBots: excludeBots.toString(),
});
const response = await fetch(`/api/leaderboard/${serverId}?${params}`);
const result = await response.json();
setData(result);
setLoading(false);
}
fetchLeaderboard();
}, [serverId, type, excludeBots]);
if (loading) return <div>Loading...</div>;
return (
<div className="leaderboard">
<div className="controls">
<select value={type} onChange={(e) => setType(e.target.value)}>
<option value="messages">Messages</option>
<option value="threads">Threads</option>
<option value="reactions">Reactions</option>
</select>
<label>
<input
type="checkbox"
checked={excludeBots}
onChange={(e) => setExcludeBots(e.target.checked)}
/>
Exclude bots
</label>
</div>
<ol className="rankings">
{data.leaderboard.map((entry) => (
<LeaderboardEntry key={entry.userId} entry={entry} type={type} />
))}
</ol>
<div className="cache-info">
Updated {formatRelative(data.cachedAt)}
</div>
</div>
);
}

Leaderboard Entry

function LeaderboardEntry({ entry, type }) {
const avatarUrl = entry.avatar
? `https://cdn.discordapp.com/avatars/${entry.userId}/${entry.avatar}.png?size=64`
: `https://cdn.discordapp.com/embed/avatars/${(BigInt(entry.userId) >> 22n) % 6n}.png`;
const label = {
messages: 'messages',
threads: 'threads created',
reactions: 'reactions received',
}[type];
return (
<li className={`entry rank-${entry.rank}`}>
<span className="rank">{entry.rank}</span>
<img className="avatar" src={avatarUrl} alt="" />
<div className="info">
<span className="name">
{entry.globalName || entry.username}
{entry.isBot && <span className="bot-tag">BOT</span>}
</span>
<span className="username">@{entry.username}</span>
</div>
<div className="count">
<span className="number">{entry.count.toLocaleString()}</span>
<span className="label">{label}</span>
</div>
</li>
);
}

Podium Display

function Podium({ leaderboard }) {
const [second, first, third] = [
leaderboard[1],
leaderboard[0],
leaderboard[2],
];
return (
<div className="podium">
{second && <PodiumPlace entry={second} place={2} />}
{first && <PodiumPlace entry={first} place={1} />}
{third && <PodiumPlace entry={third} place={3} />}
</div>
);
}
function PodiumPlace({ entry, place }) {
const heights = { 1: 120, 2: 90, 3: 70 };
const medals = { 1: '🥇', 2: '🥈', 3: '🥉' };
return (
<div className={`podium-place place-${place}`}>
<img
className="avatar"
src={getAvatarUrl(entry)}
alt={entry.username}
/>
<span className="medal">{medals[place]}</span>
<span className="name">{entry.globalName || entry.username}</span>
<span className="count">{entry.count.toLocaleString()}</span>
<div className="platform" style={{ height: heights[place] }} />
</div>
);
}

CSS Styling

.leaderboard {
max-width: 600px;
margin: 0 auto;
}
.leaderboard .controls {
display: flex;
gap: 16px;
margin-bottom: 16px;
}
.rankings {
list-style: none;
padding: 0;
margin: 0;
}
.entry {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
border-radius: 8px;
background: #f5f5f5;
margin-bottom: 8px;
}
.entry.rank-1 { background: linear-gradient(90deg, #ffd700 0%, #f5f5f5 30%); }
.entry.rank-2 { background: linear-gradient(90deg, #c0c0c0 0%, #f5f5f5 30%); }
.entry.rank-3 { background: linear-gradient(90deg, #cd7f32 0%, #f5f5f5 30%); }
.entry .rank {
font-weight: 700;
font-size: 18px;
width: 30px;
text-align: center;
}
.entry .avatar {
width: 48px;
height: 48px;
border-radius: 50%;
}
.entry .info {
flex: 1;
}
.entry .name {
font-weight: 600;
display: block;
}
.entry .username {
color: #666;
font-size: 14px;
}
.entry .count {
text-align: right;
}
.entry .count .number {
font-weight: 700;
font-size: 20px;
display: block;
}
.entry .count .label {
font-size: 12px;
color: #666;
}
/* Podium */
.podium {
display: flex;
justify-content: center;
align-items: flex-end;
gap: 16px;
padding: 32px;
}
.podium-place {
display: flex;
flex-direction: column;
align-items: center;
}
.podium-place .avatar {
width: 64px;
height: 64px;
border-radius: 50%;
border: 3px solid #5865f2;
}
.podium-place .platform {
width: 100px;
background: linear-gradient(180deg, #5865f2, #4752c4);
border-radius: 8px 8px 0 0;
margin-top: 8px;
}