cjmellor/level-up
Level Up is a Laravel gamification package for adding experience points, levels, achievements, and badges to your app. Award XP for user actions, define level thresholds, unlock achievements, and track progress with simple traits, models, and events.
Leaderboard::board() — a "Top 10 this week", "Friends leaderboard", or "Guild ranking" becomes a product surface rather than an engineering epic. restrictTo() covers friends lists, cohorts, guilds, and tournament brackets with ranks computed inside the restricted population, which is the difference between a global board nobody can win and a local board everyone can.period(Period::Day|Week|Month) and since(start:, until:) make weekly resets, monthly contests, and launch-window events first-class. week_starts_on and timezone are config knobs, so a PM can specify "resets Monday 00:00 local" without a spec negotiation.LeaderboardRankChanged, UserEnteredTrackedDepth, and UserLeftTrackedDepth. That is the raw material for "you dropped to 4th", "you're 2 spots from promotion", and "you entered the top 100" messaging — the highest-leverage retention copy, delivered by the package rather than invented per feature.currentDivision(), cohortStandings(), and a rollover command that promotes/relegates and fires UserDivisionChanged. This is a proven retention structure that's expensive to get right (cohort sizing, tie handling, rollover idempotency) and it ships as a command you schedule.count on achievements means "50 games played" or "12 articles read" can be shown as a genuine progress bar rather than a percentage abstraction — a meaningful UX upgrade for onboarding checklists and goal-gradient screens.level, streak, achievements, and challenges metrics (plus custom RankingMetric implementations) let you rank on the behaviour you actually want to reward. If the product goal is consistency rather than volume, you can board on streaks without touching the points economy.ShouldDispatchAfterCommit and mutations are wrapped in DB::transaction(). That removes a class of "user got the reward email for points that rolled back" incidents, which is the kind of thing that quietly blocks gamification launches in finance- and entitlement-sensitive products.true and time-windowed XP boards read from the experience_audits ledger — you get a queryable progression history for cohort analysis ("do users who hit level 5 in week 1 retain?") without a separate instrumentation project.Consider it when:
User model and a relational database, and gamification is a feature, not the product itself.level-up:snapshot-boards, level-up:league-rollover) and own their operational health — missed runs mean stale boards and un-promoted users, which is a visible product failure.AUDIT_POINTS=false triggers MetricRequiresAuditingException for those metrics — a deliberate either/or).Look elsewhere when:
User model. The removal of trait method aliasing means colliding challenges() / streaks() / experience() / experienceHistory() methods must be renamed or composed into a wrapper model — a refactor that touches every call site.Upgrade posture (v2.x → v3.0.0): treat this as a planned migration, not a patch. The Multiplier::scopeTo() removal, the multiplier_scopes polymorphic table being replaced by multiplier_user / multiplier_tier pivots (with an automatic backfill migration), and the removed MultiplierScope model and config keys are all breaking. Leaderboard::generate() now returns LeaderboardEntry objects (->user, ->score, ->rank) instead of User collections, so any existing board UI or API contract needs updating. setPoints() now recalculates level and tier and fires UserLevelledUp / UserTierUpdated — admin tooling that previously wrote points silently will now trigger side effects and notifications. Budget a sprint, rehearse the backfill on a production-shaped dataset, and verify the alter_experience_audits_type_to_string down-migration on PostgreSQL if you support rollbacks.
To executives: This package gives us a mature engagement stack — points, levels, achievements, streaks, leaderboards, and promotion/relegation leagues — as a dependency instead of a quarter of engineering. The v3 release is the one that makes it strategically interesting: competitive boards, re-engagement events, and seasonal resets are the mechanics that move retention, and they're now configuration rather than bespoke code. The commercial case is time-to-market and optionality: we can ship one loop (weekly leaderboard), measure it, and add or remove the next (leagues) without a new build each time. It requires a database version we should confirm (MySQL 8+ or Postgres), and if we're behind on that, the upgrade is worth doing on its own merits.
To engineering: It's a well-scoped domain package with a clear ownership boundary — progression state in our database, ranking computed in SQL with window functions, and lifecycle handled by two scheduled commands. v3 is a breaking major: typed multiplier pivots replace the polymorphic table (with an automatic backfill), scopeTo() becomes scopeToUser() / scopeToTier(), leaderboards return LeaderboardEntry value objects, and trait aliasing helpers are gone. The wins are real though — point mutations are now transactional, package events implement ShouldDispatchAfterCommit so listeners don't fire on rolled-back transactions, achievement relations are invalidated on mutation instead of needing manual refresh(), and setPoints() no longer silently desyncs level and tier. Risk is concentrated in the migration and the leaderboard return-type change; everything else is mechanical.
To the product and design team: We get progress bars backed by real counts ("50 games played", not "72%"), time-windowed boards that reset on a schedule we specify (including timezone and week-start), friends/guild-scoped rankings, and a division ladder with promotion and relegation. We also get the notification hooks for "you moved up / you're about to drop" without asking engineering to instrument anything new. Two constraints to design around: XP-based periodic boards depend on auditing being enabled (a storage decision, not a free toggle), and ranks are computed on a schedule via snapshots — so boards are near-real-time, not live, and copy should reflect that.
How can I help you explore Laravel packages today?