Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Badges Laravel Package

maize-tech/laravel-badges

Laravel Badges lets you attach badges and achievements to any Eloquent model via a simple trait. Define badges, assign or revoke them, check ownership, and build gamification features like user rewards with a clean, fluent API.

View on GitHub
Deep Wiki
Context7

Getting Started

This release (1.2.0) is a maintenance drop: PHPStan cleanups, a CI action bump, and — most importantly — Laravel 12 support. There are no breaking changes, so existing installs on 1.1.0 can upgrade freely.

Minimal setup:

composer require maize-tech/laravel-badges
php artisan vendor:publish --provider="Maize\Badges\BadgesServiceProvider" --tag="badges-migrations"
php artisan migrate

Then add the package trait to any model that should be able to receive badges:

use Maize\Badges\HasBadges;

class User extends Authenticatable
{
    use HasBadges;
}

The first productive use case is awarding a badge to a user when they hit a milestone:

$badge = Badge::create([
    'name' => 'Early Adopter',
    'description' => 'Joined in the first week',
]);

$user->assignBadge($badge);

Before writing feature code, check two things in the repo:

  1. The published migration — confirm the table name and whether the polymorphic columns are indexed in your app.
  2. The Badge model contract — whether name is unique and what fields are fillable, so seeders don't fail silently.

If you're on Laravel 12, 1.2.0 is the first version to add it; pin ^1.2 in composer.json rather than ^1.1.

Implementation Patterns

Seeding a badge catalogue. Treat badges as reference data, not user-generated content. Define them in a seeder and look them up by a stable key (slug or name) rather than hardcoding IDs:

$badge = Badge::firstOrCreate(
    ['name' => 'profile-complete'],
    ['description' => 'Filled in every profile field']
);

Awarding from domain events. The cleanest integration is a listener on your own events, keeping badge logic out of controllers:

class AwardProfileBadge
{
    public function handle(ProfileCompleted $event): void
    {
        $event->user->assignBadge(
            Badge::where('name', 'profile-complete')->firstOrFail()
        );
    }
}

Making assignment idempotent. Wrap assignment in a check so repeated events don't duplicate rows or throw on a unique constraint:

if (! $user->hasBadge($badge)) {
    $user->assignBadge($badge);
}

Displaying in Blade / API. Eager-load the relationship to avoid N+1 in listings:

$users = User::with('badges')->paginate();

Then map to a resource so the frontend isn't coupled to the package's column names:

'badges' => $user->badges->map(fn ($b) => [
    'name' => $b->name,
    'description' => $b->description,
]);

Testing. Use the trait on your User factory model and assert against the relationship rather than the package internals:

$user->assignBadge($badge);
$this->assertTrue($user->fresh()->hasBadge($badge));

Because assignment goes through a polymorphic relation, tests that swap models or use RefreshDatabase with multiple badgeable types should assert on badgeable_type explicitly if you're using a morph map.

Gotchas and Tips

Upgrade on 1.2.0 is safe, but verify Laravel 12 constraints. The release adds Laravel 12 support alongside existing versions. If you're pinned to Laravel 11, nothing regresses — but if you're upgrading to Laravel 12, this is the minimum version to use. Run composer why-not maize-tech/laravel-badges if the install won't resolve.

PHPStan fixes are not runtime behaviour. The two PHPStan PRs may change type hints or docblocks. If you extend the package's classes or override methods, re-run your own static analysis after upgrading rather than assuming a patch-only bump is inert.

Polymorphic duplicates. Many badge packages allow the same badge to be attached multiple times to the same model. If you need "award once" semantics, enforce it at the database level with a unique index on (badge_id, badgeable_type, badgeable_id) — relying on application-layer checks alone will race under concurrency.

Morph map collisions. If you use Relation::enforceMorphMap(), the badgeable_type values already stored will not match new morph aliases. Decide on the morph map before awarding any badges in production, or write a migration to rewrite existing rows.

Revoking and re-awarding. Confirm how the package detaches badges (revokeBadge / detach). If revocation is a hard delete, you lose the audit trail; if that matters, add your own awarded_at column via a migration on the published pivot/model table rather than fighting the package schema.

Cache invalidation. Badge counts are a common dashboard widget. If you cache counts, invalidate on the badges() relation changes — the package won't emit events for you unless the release notes say so, so hook into your own assignment service rather than calling the trait directly from many call sites.

Debugging missing badges. When $user->badges is empty despite a successful assignBadge, check in this order: (1) the migration actually ran, (2) the trait is on the exact model class being saved, (3) the morph type stored matches the class or alias, (4) you're reading a stale model instance — $user->refresh()->badges resolves most confusion.

Extension point. The package's service provider is the place to swap implementations. If you need custom badge metadata beyond name/description, prefer adding columns to the badges table via your own migration over forking the package — that survives upgrades to 1.3+ cleanly.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle