A zero-dependency, fully typed TypeScript package for XP-based leveling systems.
π Language:
- πΊπΈ English (this document)
- π§π· Portuguese: README.pt-BR.md
npm install xp-level-system
# or
yarn add xp-level-systemimport { XPSystem } from "xp-level-system";
const sys = new XPSystem({ baseXP: 100, curve: "quadratic" });
// XP β Level
const info = sys.getLevelInfo(1000);
console.log(info.level); // 4
console.log(info.progressPercent); // 14%
// Level β Required total XP
console.log(sys.xpForLevel(3)); // 400| Option | Type | Default | Description |
|---|---|---|---|
baseXP |
number |
100 |
Base XP used in calculations |
multiplier |
number |
1.5 |
Multiplier used by the exponential curve |
curve |
LevelCurve |
"quadratic" |
Growth formula |
customFormula |
(level) => number |
β | Custom formula (required when curve = "custom") |
minLevel |
number |
1 |
Minimum level |
maxLevel |
number |
Infinity |
Maximum level cap |
| Curve | Formula (total XP required to reach level) |
|---|---|
linear |
baseXP Γ (level β 1) |
quadratic |
baseXP Γ (level β 1)Β² |
exponential |
baseXP Γ (multiplier^(levelβ1) β 1) |
custom |
customFormula(level) |
Returns a complete progression snapshot based on accumulated XP.
const info = sys.getLevelInfo(1000);
info.level // 4 β current level
info.nextLevel // 5 β next level
info.totalXP // 1000 β accumulated XP
info.currentXP // 100 β XP within current level
info.xpToNextLevel // 700 β XP required to level up
info.progressPercent // 14 β percentage (0β100)
info.isMaxLevel // false β whether max level has been reachedReturns the total accumulated XP required to be at level.
sys.xpForLevel(1) // 0
sys.xpForLevel(3) // 400
sys.xpForLevel(5) // 1600Converts total XP into a level.
sys.levelFromXP(1000) // 4
sys.levelFromXP(400) // 3Adds XP and returns the result, including how many levels were gained.
const result = sys.addXP(350, 500);
result.levelInfo // Updated LevelInfo
result.levelsChanged // +2 (gained 2 levels)
result.didLevelUp // true
result.xpDelta // 500Removes XP and returns the result, including how many levels were lost.
const result = sys.removeXP(1500, 700);
result.levelInfo // Updated LevelInfo
result.levelsChanged // -1 (lost 1 level)
result.didLevelDown // true
result.xpDelta // -700Promotes the player directly to the next level (useful for admin actions).
Returns an unchanged state if already at maxLevel.
const result = sys.levelUp(450);
result.levelInfo.level // 4 (was level 3)Returns the XP required within a specific level (delta between two thresholds).
sys.xpRequiredForLevel(3) // 500 (from 400 to 900)Formats a LevelInfo object into a user-friendly string.
sys.format(info) // "Level 4 (100 / 700 XP β 14%)"XPSystem (and Player, below) emit events on every mutation, so you can drive UI
feedback β toasts, sound effects, confetti β without polling.
const sys = new XPSystem({ baseXP: 100, curve: "quadratic" });
sys.on("xpGained", (result) => console.log(`+${result.xpDelta} XP`));
sys.on("levelUp", (result) => console.log(`π Level ${result.levelInfo.level}!`));
sys.on("maxLevelReached", (info) => console.log(`Max level reached: ${info.level}`));
sys.addXP(0, 500);Available events: xpGained, xpLost, levelUp, levelDown, maxLevelReached.
Every on() call returns an unsubscribe function; once() and removeAllListeners()
are also available.
// Double-XP weekend event
sys.addXP(currentXP, 100, { multiplier: 2, reason: "event:double-xp" });
// Quest reward, tagged for analytics/logging
sys.addXP(currentXP, 250, { reason: "quest:dragon-slay" });Configure named tiers (Bronze/Silver/Gold, or anything else) and resolve the current one β plus the next tier and progress toward it β from a level:
const sys = new XPSystem({
baseXP: 100,
curve: "quadratic",
ranks: [
{ name: "Bronze", minLevel: 1, color: "#cd7f32" },
{ name: "Silver", minLevel: 5, color: "#c0c0c0" },
{ name: "Gold", minLevel: 10, color: "#ffd700" },
{ name: "Diamond", minLevel: 20, color: "#b9f2ff" },
],
});
const rank = sys.getRank(7);
rank.name // "Silver"
rank.next.name // "Gold"
rank.progressToNext // 40 (2 of 5 levels toward Gold)AchievementTracker is a standalone, persistence-friendly badge engine β use it directly,
or let Player (below) drive it automatically.
import { AchievementTracker, levelAchievement, xpAchievement } from "xp-level-system";
const tracker = new AchievementTracker([
levelAchievement("veteran", 10, { name: "Veteran", icon: "ποΈ" }),
xpAchievement("grinder", 10_000, { name: "Grinder", icon: "βοΈ" }),
{ id: "secret", name: "???", hidden: true, condition: (ctx) => ctx.totalXP > 99_999 },
]);
const unlocked = tracker.evaluate({ level: 10, totalXP: 900 });
// β [{ id: "veteran", name: "Veteran", icon: "ποΈ", unlockedAt: 1699999999999 }]
tracker.getCompletionPercent(); // 33
tracker.getLocked(); // remaining achievements (hidden ones show "???")
// Persist / restore
const saved = tracker.toJSON();
const restored = AchievementTracker.fromJSON(saved, definitions);XPSystem is deliberately stateless (you own the persistence). Player is the batteries-
included alternative: it holds totalXP internally, wires up ranks/achievements/prestige,
and emits events automatically β the fastest way to model a game character.
import { Player, levelAchievement } from "xp-level-system";
const player = new Player({
xpSystem: {
baseXP: 100,
curve: "quadratic",
ranks: [
{ name: "Bronze", minLevel: 1 },
{ name: "Silver", minLevel: 5 },
{ name: "Gold", minLevel: 10 },
],
},
achievements: [levelAchievement("veteran", 10, { name: "Veteran" })],
prestige: { enabled: true, requiredLevel: 10, bonusPerPrestige: 0.1 },
});
player.on("levelUp", (r) => console.log(`Level ${r.levelInfo.level}!`));
player.on("achievementUnlocked", (a) => console.log(`Unlocked: ${a.name}`));
player.on("prestige", (p) => console.log(`Prestige ${p.prestige}!`));
player.gainXP(500); // grants XP, auto-checks achievements
player.level; // current level
player.rank; // current RankInfo | null
player.progress; // full LevelInfo snapshot
player.canPrestige(); // true once requiredLevel is reached
player.prestige(); // resets XP to 0, +1 prestige, permanent XP bonus
// Persistence β store `player.toJSON()` in your database, restore later:
const saved = player.toJSON(); // { totalXP, prestige, unlockedAchievements }
const restored = Player.fromJSON(saved, sameConfig);Pure helper for ranking players by XP (standard competition ranking β ties share a position):
import { buildLeaderboard } from "xp-level-system";
const board = buildLeaderboard(
[
{ id: "alice", totalXP: 1600, name: "Alice" },
{ id: "bob", totalXP: 900, name: "Bob" },
],
sys
);
// [{ id: "alice", ..., position: 1, level: 5 },
// { id: "bob", ..., position: 2, level: 4 }]Level 1 β 0 XP
Level 2 β 100 XP
Level 3 β 400 XP
Level 4 β 900 XP
Level 5 β 1,600 XP
Level 1 β 0 XP
Level 2 β 500 XP
Level 3 β 1,000 XP
Level 4 β 1,500 XP
Level 1 β 0 XP
Level 2 β 100 XP
Level 3 β 300 XP
Level 4 β 700 XP
Level 5 β 1,500 XP
Level 6 β 3,100 XP
const sys = new XPSystem({
curve: "custom",
customFormula: (level) => level * (level + 1) * 50,
});import type {
// Core
XPSystemConfig,
LevelCurve,
LevelInfo,
XPChangeResult,
XPChangeOptions,
XPSystemEvents,
// Ranks
RankDefinition,
RankInfo,
// Achievements
AchievementContext,
AchievementDefinition,
UnlockedAchievement,
AchievementTrackerState,
// Player
PlayerConfig,
PlayerState,
PrestigeConfig,
PrestigeResult,
PlayerEvents,
// Leaderboard
LeaderboardEntry,
RankedLeaderboardEntry,
} from "xp-level-system";Persist only totalXP β every other field is derived from it:
// Save
await db.user.update({ totalXP: result.levelInfo.totalXP });
// Load
const user = await db.user.findUnique({ where: { id } });
const info = sys.getLevelInfo(user.totalXP);When using Player, persist player.toJSON() instead (it also captures prestige and
unlocked achievements):
// Save
await db.user.update({ progression: player.toJSON() });
// Load
const user = await db.user.findUnique({ where: { id } });
const player = Player.fromJSON(user.progression, config);The example/ folder contains a full Next.js "game dashboard" β animated XP
bar, rank badges, achievement grid, prestige button, and a leaderboard β built entirely on
top of this package. Deploy it to Vercel in one click:
cd example
npm install
npm run devSee example/README.md for the Vercel deploy instructions.