API reference
Complete TypeScript API reference for the XP plugin.
All exports are available from the @robojs/xp package. The plugin exports XP (also aliased as xp), config, events, leaderboard, math, rewards, constants, formatXP, and getXpLabel.
XP manipulation
The XP object (also exported as xp) provides methods for reading and modifying user XP.
XP.addXP
Add XP to a user. Creates the user record if it doesn't exist.
import { XP } from '@robojs/xp'
const result = await XP.addXP(guildId, userId, 100, { reason: 'contest_winner' })import { XP } from '@robojs/xp'
const result = await XP.addXP(guildId, userId, 100, { reason: 'contest_winner' })Parameters:
| Parameter | Type | Description |
|---|---|---|
guildId | string | Guild snowflake |
userId | string | User snowflake |
amount | number | XP to add (must be >= 0) |
options.reason | string? | Reason included in events |
options.storeId | string? | Store to use (default: 'default') |
Returns: XPChangeResult
interface XPChangeResult {
oldXp: number
newXp: number
oldLevel: number
newLevel: number
leveledUp: boolean
}XP is capped at the curve's maxLevel threshold if one is defined. Also available as XP.add().
XP.removeXP
Remove XP from a user. XP is clamped at 0.
const result = await XP.removeXP(guildId, userId, 50, { reason: 'moderation' })const result = await XP.removeXP(guildId, userId, 50, { reason: 'moderation' })Parameters: Same as addXP.
Returns: XPRemoveResult
interface XPRemoveResult {
oldXp: number
newXp: number
oldLevel: number
newLevel: number
leveledDown: boolean
}Also available as XP.remove().
XP.setXP
Set a user's total XP to an exact value.
const result = await XP.setXP(guildId, userId, 5000, { reason: 'admin_adjustment' })const result = await XP.setXP(guildId, userId, 5000, { reason: 'admin_adjustment' })Parameters: Same as addXP, but amount is the absolute XP value.
Returns: XPSetResult
interface XPSetResult {
oldXp: number
newXp: number
oldLevel: number
newLevel: number
}Also available as XP.set().
XP.recalc
Recompute a user's level from their stored total XP and reconcile role rewards. Idempotent.
const result = await XP.recalc(guildId, userId)const result = await XP.recalc(guildId, userId)Parameters:
| Parameter | Type | Description |
|---|---|---|
guildId | string | Guild snowflake |
userId | string | User snowflake |
options.storeId | string? | Store to use (default: 'default') |
Returns: RecalcResult
interface RecalcResult {
oldLevel: number
newLevel: number
totalXp: number
reconciled: boolean // true if level was corrected
}XP.getXP
Get a user's total XP. Returns 0 if the user has no record.
const xp = await XP.getXP(guildId, userId)
const repXp = await XP.getXP(guildId, userId, { storeId: 'reputation' })const xp = await XP.getXP(guildId, userId)
const repXp = await XP.getXP(guildId, userId, { storeId: 'reputation' })Also available as XP.get().
XP.getLevel
Get a user's current level. Returns 0 if the user has no record.
const level = await XP.getLevel(guildId, userId)const level = await XP.getLevel(guildId, userId)XP.getUser
Get the full user record. Returns null if the user has no record.
const user = await XP.getUser(guildId, userId)const user = await XP.getUser(guildId, userId)Returns: UserXP | null
interface UserXP {
xp: number // Total XP accumulated
level: number // Current level
lastAwardedAt: number // Unix timestamp (ms) of last XP award
messages: number // Total messages in guild text channels
xpMessages: number // Messages that actually awarded XP
}The messages counter increments for all valid guild messages. The xpMessages counter only increments when XP is awarded (after cooldown, no-XP zone, and multiplier checks pass).
Configuration
See Configuration for full details on each config field.
config.get
Get the merged config for a guild. Never returns null.
import { config } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const repConfig = await config.get(guildId, { storeId: 'reputation' })import { config } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const repConfig = await config.get(guildId, { storeId: 'reputation' })config.set
Update guild config with partial values. Validates before persisting.
const updated = await config.set(guildId, {
cooldownSeconds: 45,
xpRate: 1.5
})const updated = await config.set(guildId, {
cooldownSeconds: 45,
xpRate: 1.5
})Returns the full merged config after the update.
config.validate
Validate a partial config object without persisting.
const { valid, errors } = config.validate(partialConfig)const { valid, errors } = config.validate(partialConfig)Returns { valid: boolean, errors: string[] }.
config.getGlobal / config.setGlobal
Manage global defaults that apply to all guilds.
const globalConfig = await config.getGlobal()
await config.setGlobal({ xpRate: 1.5 })const globalConfig = await config.getGlobal()
await config.setGlobal({ xpRate: 1.5 })config.getDefault
Returns system defaults before any global or guild overrides.
const defaults = config.getDefault()const defaults = config.getDefault()Leaderboard
leaderboard.get
Get paginated leaderboard entries.
import { leaderboard } from '@robojs/xp'
const result = await leaderboard.get(guildId, 0, 10)
// { entries: LeaderboardEntry[], total: number }import { leaderboard } from '@robojs/xp'
const result = await leaderboard.get(guildId, 0, 10)
// { entries: LeaderboardEntry[], total: number }Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
guildId | string | -- | Guild snowflake |
offset | number | 0 | Starting position |
limit | number | 10 | Max entries to return |
options.storeId | string? | 'default' | Store to query |
Returns: { entries: LeaderboardEntry[], total: number }
interface LeaderboardEntry {
userId: string
xp: number
level: number
rank: number // 1-indexed
}Results for offset + limit of 100 or less come from the cache (sub-10ms). Deeper pagination triggers a full dataset sort.
leaderboard.getRank
Get a specific user's rank.
const rankInfo = await leaderboard.getRank(guildId, userId)
// { rank: 1, total: 150 } or nullconst rankInfo = await leaderboard.getRank(guildId, userId)
// { rank: 1, total: 150 } or nullReturns null if the user has no XP or XP is 0.
leaderboard.invalidateCache
Manually invalidate the leaderboard cache.
leaderboard.invalidateCache(guildId)
leaderboard.invalidateCache(guildId, { storeId: 'reputation' })
leaderboard.invalidateCache(guildId, { all: true }) // All storesleaderboard.invalidateCache(guildId)
leaderboard.invalidateCache(guildId, { storeId: 'reputation' })
leaderboard.invalidateCache(guildId, { all: true }) // All storesEvents
See Events for full documentation on the event system.
import { events } from '@robojs/xp'
events.onLevelUp(handler)
events.onLevelDown(handler)
events.onXPChange(handler)
events.on('levelUp', handler)
events.once('xpChange', handler)
events.off('levelUp', handler)import { events } from '@robojs/xp'
events.onLevelUp(handler)
events.onLevelDown(handler)
events.onXPChange(handler)
events.on('levelUp', handler)
events.once('xpChange', handler)
events.off('levelUp', handler)Math utilities
Pure math functions for level curve calculations. All functions accept an optional LevelCurve parameter to use a custom curve instead of the default quadratic.
math.xpNeededForLevel
XP delta required to gain a specific level (from the previous level).
import { math } from '@robojs/xp'
math.xpNeededForLevel(1) // 155
math.xpNeededForLevel(10) // 1100import { math } from '@robojs/xp'
math.xpNeededForLevel(1) // 155
math.xpNeededForLevel(10) // 1100math.totalXpForLevel
Cumulative XP required to reach the start of a level.
math.totalXpForLevel(10) // 5675math.totalXpForLevel(10) // 5675math.computeLevelFromTotalXp
Compute level and progress from total XP.
const progress = math.computeLevelFromTotalXp(5500)
// { level: 9, inLevel: 925, toNext: 175 }const progress = math.computeLevelFromTotalXp(5500)
// { level: 9, inLevel: 925, toNext: 175 }Returns: LevelProgress
interface LevelProgress {
level: number // Current level
inLevel: number // XP accumulated within the current level
toNext: number // XP needed for the next level (0 at maxLevel)
}math.progressInLevel
Get progress percentage within the current level.
const { current, needed, percentage } = math.progressInLevel(5500)
// { current: 925, needed: 1100, percentage: 84.09 }const { current, needed, percentage } = math.progressInLevel(5500)
// { current: 925, needed: 1100, percentage: 84.09 }math.xpDeltaForLevelRange
XP difference between two levels. Can be negative if toLevel < fromLevel.
const delta = math.xpDeltaForLevelRange(5, 10) // XP needed from level 5 to 10const delta = math.xpDeltaForLevelRange(5, 10) // XP needed from level 5 to 10math.isValidLevel / math.isValidXp
Validate level and XP values.
math.isValidLevel(50) // true
math.isValidLevel(-1) // false
math.isValidXp(10000) // true
math.isValidXp(-5) // falsemath.isValidLevel(50) // true
math.isValidLevel(-1) // false
math.isValidXp(10000) // true
math.isValidXp(-5) // falseRewards
rewards.reconcile
Manually reconcile a user's roles based on their level and the guild config.
import { rewards, config, XP } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const userLevel = await XP.getLevel(guildId, userId)
await rewards.reconcile(guildId, userId, userLevel, guildConfig)import { rewards, config, XP } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const userLevel = await XP.getLevel(guildId, userId)
await rewards.reconcile(guildId, userId, userLevel, guildConfig)Parameters:
| Parameter | Type | Description |
|---|---|---|
guildId | string | Guild snowflake |
userId | string | User snowflake |
newLevel | number | User's current level |
guildConfig | GuildConfig | Full guild configuration |
Also available as rewards.reconcileRewards() and as the top-level export reconcileRewards().
Constants
import { constants } from '@robojs/xp'
constants.DEFAULT_COOLDOWN // 60
constants.DEFAULT_XP_RATE // 1.0
constants.DEFAULT_REWARDS_MODE // 'stack'
constants.DEFAULT_REMOVE_ON_LOSS // false
constants.DEFAULT_LEADERBOARD_PUBLIC // false
constants.DEFAULT_CURVE_A // 5
constants.DEFAULT_CURVE_B // 50
constants.DEFAULT_CURVE_C // 100import { constants } from '@robojs/xp'
constants.DEFAULT_COOLDOWN // 60
constants.DEFAULT_XP_RATE // 1.0
constants.DEFAULT_REWARDS_MODE // 'stack'
constants.DEFAULT_REMOVE_ON_LOSS // false
constants.DEFAULT_LEADERBOARD_PUBLIC // false
constants.DEFAULT_CURVE_A // 5
constants.DEFAULT_CURVE_B // 50
constants.DEFAULT_CURVE_C // 100The curve constants (DEFAULT_CURVE_A, DEFAULT_CURVE_B, DEFAULT_CURVE_C) are also available as individual named exports.
Utility functions
formatXP
Format an XP value with thousands separators and an optional label.
import { formatXP } from '@robojs/xp'
formatXP(1500) // '1,500 XP'
formatXP(1500, 'Reputation') // '1,500 Reputation'import { formatXP } from '@robojs/xp'
formatXP(1500) // '1,500 XP'
formatXP(1500, 'Reputation') // '1,500 Reputation'getXpLabel
Extract the custom XP label from a guild config.
import { getXpLabel, config } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const label = getXpLabel(guildConfig) // 'XP' or custom labelimport { getXpLabel, config } from '@robojs/xp'
const guildConfig = await config.get(guildId)
const label = getXpLabel(guildConfig) // 'XP' or custom label