// the find
cjmellor/level-up
Level-Up is a Laravel package introducing gamification into your applications. Users earn experience points (XP) and levels through interactions, while also unlocking achievements. It promotes engagement, competition, and fun through its dynamic leaderboard feature. Customisable to fit your specific needs
A Laravel package that bolts an entire gamification layer — XP, levels, achievements, multipliers, tiers, streaks, challenges, and a leaderboard with leagues/divisions — onto your User model via traits. Aimed at Laravel devs building apps with engagement mechanics who don't want to hand-roll ranking SQL and point ledgers themselves.
The leaderboard is built properly: ranks are computed with SQL window functions using competition semantics (ties share a rank, next rank is skipped), with deterministic tie-breaking so pagination boundaries don't shift between requests — most homegrown leaderboards get this wrong. Periodic boards (this week's XP) are sourced from an experience_audits ledger rather than naive timestamp scans, and the package is explicit about when that requires auditing enabled vs. throwing. The snapshot/diff system for rank-change events (LeaderboardRankChanged, UserEnteredTrackedDepth) is a real feature most people skip building because it's annoying — diffing two point-in-time snapshots to get deltas isn't something a stateless query can do. Configurable table names, prefixes, and PK types (bigint/uuid/ulid) mean you can retrofit this into an app that already has conflicting table names without touching the published migrations.
Achievements have zero built-in tooling — there's no API for defining criteria or evaluating progress triggers, just a bare Eloquent model, so the actual 'when does someone earn this' logic is entirely on you. The feature surface is large (tiers, multipliers, challenges, leagues, cohorts, divisions, streaks) and that's a lot of config and domain concepts to internalize if you just wanted simple XP and levels. The 'silent by design' behavior below tracked depth (no snapshot rows, no events if a user ranks below track_top) is a reasonable tradeoff but easy to get bitten by if you don't read the docs and just assume rank-change events fire for everyone. Multiplier stacking strategy (compound/additive/highest) is a single global config value, so you can't have different stacking rules for different point sources without extending the package yourself.