insynnia / laravel-badges
Badge, trigger and award engine for Laravel. Award badges to entities via named triggers or manually โ idempotent and framework-native.
Requires
- php: ^8.2
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- pestphp/pest: ^3.0|^4.0
README
๐ Laravel Badges
A small, framework-native badge & achievement engine for Laravel.
Define badges, expose named triggers, and award them to entities โ idempotently.
No multi-tenancy, no billing, no dashboard. Just the badge logic โ drop it into any Laravel app and start awarding.
โจ Features
- ๐๏ธ Badges with name, description, image, category and tier (
bronzeโplatinum). - โก Triggers โ award a badge by firing a named slug (
first-login,100-sales, โฆ). - ๐ชช Entities keyed by your
external_id, auto-created on first award. - โป๏ธ Idempotent awards โ firing twice never duplicates.
- ๐๏ธ Metadata โ attach arbitrary JSON to badges, entities and awards.
- ๐งฉ Bring your own HTTP โ ships the domain logic, stays unopinionated about routes & auth.
- ๐งช Fully tested, portable across SQLite / MySQL / PostgreSQL.
๐ฆ Requirements
- PHP 8.2+
- Laravel 11, 12 or 13
๐ Installation
composer require insynnia/laravel-badges php artisan migrate
Publish config / migrations if you want to tweak them:
php artisan vendor:publish --tag=badges-config php artisan vendor:publish --tag=badges-migrations
๐ง Concepts
| Model | What it is |
|---|---|
Badge |
A named award (name, description, image, category, tier, metadata). |
Trigger |
A slug that, when fired, awards a specific badge. |
Entity |
A recipient, identified by your own external_id (e.g. a user id). |
EntityBadge |
The award record linking an entity to a badge (unique per pair). |
fireTrigger('first-login', 'user_123')
โ
โผ
Trigger(slug) โโโถ Badge โโโถ EntityBadge โโโ Entity(external_id)
(unique entity + badge)
โก Quick start
use Insynnia\Badges\BadgeService; use Insynnia\Badges\Models\Badge; use Insynnia\Badges\Models\Trigger; $badge = Badge::create(['name' => 'First Login', 'tier' => 'bronze']); Trigger::create(['badge_id' => $badge->id, 'slug' => 'first-login']); $badges = app(BadgeService::class); // Fire a trigger โ auto-creates the entity, awards the badge once. $result = $badges->fireTrigger('first-login', externalEntityId: 'user_123'); // ['awarded' => true, 'badge' => Badge, 'reason' => 'Badge awarded successfully.'] // Award directly by badge id (bypasses triggers). $badges->awardManually($badge->id, 'user_123'); // Revoke. $badges->revoke($badge->id, 'user_123');
๐ API reference
BadgeService is the entry point. fireTrigger() and awardManually() both
return array{awarded: bool, badge: ?Badge, reason: string} and are
idempotent โ a repeat award returns awarded => false.
| Method | Description |
|---|---|
fireTrigger(string $slug, string $entityId, array $metadata = []) |
Fire a trigger by slug and award its badge. |
awardManually(string $badgeId, string $entityId, array $metadata = []) |
Award a badge by id, bypassing triggers. |
revoke(string $badgeId, string $entityId): bool |
Remove an award. Returns true if a record was deleted. |
Querying
Badge::active()->byCategory('streak')->byTier('gold')->get(); $entity->badges; // badges an entity holds Badge::find($id)->entities; // entities that hold a badge $badge->image_url; // resolved via the configured disk
Badge images
image_url resolves image_path through the disk in config/badges.php
(defaults to public). Store the file yourself and persist its path:
$badge->update(['image_path' => $path]); // $badge->image_url is now resolvable
๐ HTTP layer
This package ships the domain logic only โ no routes or controllers โ so it
stays unopinionated about your API shape, auth and pagination. Wire
BadgeService into your own controllers. A typical mapping:
| Method & path | Call |
|---|---|
POST /trigger/{slug} |
fireTrigger($slug, $entityId, $meta) |
POST /entities/{id}/badges |
awardManually($badgeId, $id, $meta) |
DELETE /entities/{id}/badges/{badge} |
revoke($badgeId, $id) |
๐งช Testing
composer install
composer test
๐ค Contributing
PRs welcome. Please add a test for any behaviour change and run composer test
before opening a pull request.
๐ Security
If you discover a security issue, please email the maintainers instead of using the issue tracker.
๐ Changelog
See CHANGELOG.md for what has changed recently.
๐ License
The MIT License (MIT). See LICENSE.