Guides
Levels and XP
Give members XP for chatting, announce level-ups, and add /rank and /leaderboard commands.
You will build a level system:
- Members get 15 to 25 XP (experience points) for chatting. At most once a minute, so spam does not help.
- The bot posts a message when someone reaches a new level.
/rankshows a member's level and XP./leaderboardshows the top 10.
Premium needed
XP for chatting needs a Message event. Message events run only on servers with a premium plan. /rank and /leaderboard work on every plan.
What you need#
- A premium plan on the server.
- About 10 minutes.
The XP is saved in a Mongo database. That is a place to keep data that lasts, with one row (called a document) per member. You do not need to set it up. It is made the first time the code saves something. Learn more in Saving data.
How levels work#
A member's level comes from their XP:
| Level | XP needed |
|---|---|
| 1 | 100 |
| 2 | 400 |
| 3 | 900 |
| 5 | 2,500 |
| 10 | 10,000 |
The rule is: level = square root of (XP ÷ 100). Each level takes a bit longer than the last.
1. Give XP for messages#
On the dashboard, open Create → Event.
| Field | Value |
|---|---|
| Name | XP for messages |
| Type | MESSAGE (Pro Only) |
Paste this in Code:
def level_of(xp):
return int((xp / 100) ** 0.5)
# Bots and webhooks do not earn XP.
if user and not user.is_bot:
levels = SlashMongo("levels")
uid = str(user.id)
now = int(unix_timestamp)
doc = await levels.find_one({"_id": uid}) or {"xp": 0, "last": 0}
# One reward per minute.
if now - doc.get("last", 0) >= 60:
old_xp = doc.get("xp", 0)
new_xp = old_xp + randomNumber(15, 25)
await levels.update_one(
{"_id": uid},
{"$set": {"xp": new_xp, "last": now}},
upsert=True,
)
if level_of(new_xp) > level_of(old_xp):
send_message(
channel,
f"{user.mention} reached **level {level_of(new_xp)}**!",
)Press save.
What the code does:
- Skips bots.
- Loads the member's row. A new member starts at 0 XP.
- Stops if they got XP less than 60 seconds ago.
- Adds 15 to 25 XP and saves.
upsert=Truemakes the row if it is not there yet. - Posts a message when the level goes up.
Note
Database functions like find_one and update_one need await in front. Most other functions do not. See Storage.
2. Make /rank#
Open Create → Slash Command.
| Field | Value |
|---|---|
| Command Name | rank |
| Description | Show your level |
Under Arguments, add a Member argument:
| Field | Value |
|---|---|
| Name | member |
| Description | Whose rank to show |
| Required | Off |
Paste this in Pre-hook:
def level_of(xp):
return int((xp / 100) ** 0.5)
target = member or user
doc = await SlashMongo("levels").find_one({"_id": str(target.id)})
xp = doc["xp"] if doc else 0
level = level_of(xp)
next_xp = (level + 1) ** 2 * 100
set_content(
f"**{target.display_name}** is level **{level}**\n"
f"XP: {intcomma(xp)} / {intcomma(next_xp)}"
)Leave the Response box empty. set_content fills the reply.
When nobody is picked, member is empty, so member or user falls back to the person who ran the command.
3. Make /leaderboard#
Make another slash command named leaderboard. Paste this in Pre-hook:
def level_of(xp):
return int((xp / 100) ** 0.5)
top = await SlashMongo("levels").find({}, sort=[("xp", -1)], limit=10)
lines = []
for place, doc in enumerate(top, start=1):
lines.append(
f"**{place}.** <@{doc['_id']}> — level {level_of(doc['xp'])} "
f"({intcomma(doc['xp'])} XP)"
)
set_embed(Message(
title=f"Top members in {server.name}",
description="\n".join(lines) or "Nobody has XP yet. Start chatting!",
color="#5865F2",
))sort=[("xp", -1)] puts the highest XP first. limit=10 keeps only 10 rows, so the command stays fast on big servers.
Mentions inside an embed do not ping anyone, so the leaderboard is quiet.
4. Test it#
- Send a message in your server.
- Run
/rank. You have 15 to 25 XP. - Send another message right away. Run
/rankagain. Nothing changed: that is the one-minute wait. - Run
/leaderboard.
Not working? Open Logs → Error Logs on the dashboard. Errors from your code show there.
Ideas#
- Level roles. In the event, after the level-up message, give a role:
if level_of(new_xp) == 5: add_roles(user, "Level 5"). - No XP in some channels. Add
and channel.name != "bot-spam"to the firstif. - Image rank card. Design one in Image Templates and send it with
render_template. See Web and files. - Reset everyone. Make a staff-only command that runs
await SlashMongo("levels").delete_many({}).