elvin-qulizade / filament-loyalty
Generic points, tiers and rewards for any Filament resource
Package info
github.com/Elvin-Qulizade/filament-loyalty
pkg:composer/elvin-qulizade/filament-loyalty
Fund package maintenance!
Requires
- php: ^8.2
- filament/filament: ^5.0
- filament/forms: ^5.0
- filament/tables: ^5.0
- spatie/laravel-package-tools: ^1.15.0
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^8.0
- orchestra/testbench: ^9.0|^10.0|^11.0
- pestphp/pest: ^3.7|^4.0
- pestphp/pest-plugin-arch: ^3.0|^4.0
- pestphp/pest-plugin-laravel: ^3.0|^4.0
- pestphp/pest-plugin-livewire: ^3.0|^4.0
- rector/rector: ^2.0
- spatie/laravel-ray: ^1.26
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-04 12:04:41 UTC
README
Attach points earning, tiers, and redeemable rewards to any Eloquent model — not just customers. Earning rules are config, not code: list a model class, a condition, and how many points to award, and the package listens for you. No observers to register, no Facade calls to sprinkle through your app.
Installation
composer require elvin-qulizade/filament-loyalty
php artisan vendor:publish --tag="filament-loyalty-migrations"
php artisan migrate
php artisan vendor:publish --tag="filament-loyalty-config"
Usage
- Add the trait and contract to any Eloquent model you want to earn/spend points:
use ElvinQulizade\Loyalty\Concerns\HasLoyaltyPoints; use ElvinQulizade\Loyalty\Contracts\LoyaltyMember; class Customer extends Model implements LoyaltyMember { use HasLoyaltyPoints; }
- Configure an earning rule — no listener, no observer:
// config/filament-loyalty.php 'rules' => [ \App\Models\Order::class => [ 'member' => fn (\App\Models\Order $order) => $order->customer, 'when' => fn (\App\Models\Order $order) => $order->status === 'paid', 'points' => fn (\App\Models\Order $order) => (int) floor($order->total), ], ], 'tiers' => [ 'bronze' => 0, 'silver' => 500, 'gold' => 2000, ],
Warning
Rules use closures, so this config file cannot be cached: php artisan config:cache
fails with "Your configuration files are not serializable" as soon as a rules entry
exists. This is a known v1 limitation — don't run config:cache (or optimize) in a
deployment that uses this package's rules yet.
- Show the ledger on the member's own resource:
use ElvinQulizade\Loyalty\Filament\RelationManagers\LoyaltyRelationManager; public static function getRelations(): array { return [ LoyaltyRelationManager::class, ]; }
That's it — every order that matches your when condition earns points automatically, the
Customer's "Loyalty" tab shows the full ledger with the current balance/lifetime
points/tier, and staff can manually adjust points (with a required reason) or redeem a
reward on the member's behalf from that same tab.
Tiers are based on lifetime earned points, not current balance — redeeming a reward never demotes a member's tier.
Note
"Adjust points" and "Redeem reward" are visible to anyone who can already open the
member's resource page in your panel — there's no separate permission for them in v1.
If you need finer control, subclass LoyaltyRelationManager and override table(),
or gate access to the member resource itself.
- Manage the reward catalog by adding the resource to your panel:
use ElvinQulizade\Loyalty\Filament\Resources\LoyaltyRewardResource; public function panel(Panel $panel): Panel { return $panel ->resources([ LoyaltyRewardResource::class, ]); }
- Call the engine directly wherever code, not a panel action, should earn/spend points:
$customer->earnLoyaltyPoints(50, reason: 'Referral bonus'); $customer->redeemLoyaltyPoints(20, reason: 'Manual redemption'); $customer->adjustLoyaltyPoints(-10, 'Correcting a duplicate award'); // reason required
Drop ElvinQulizade\Loyalty\Filament\LoyaltyTierColumn::make() into any table, or
ElvinQulizade\Loyalty\Filament\LoyaltyTierEntry::make() into any infolist (e.g. the
member's own list view or view page), to show the tier badge outside the Loyalty tab too.
Other public pieces you may want directly:
use ElvinQulizade\Loyalty\Events\TierChanged; // fired by earn/adjust when loyaltyTier() changes use ElvinQulizade\Loyalty\Exceptions\InsufficientPointsException; use ElvinQulizade\Loyalty\Exceptions\RewardUnavailableException; use ElvinQulizade\Loyalty\Support\LoyaltyRewardRedeemer; // LoyaltyRewardRedeemer::redeem($member, $reward)
$member->loyaltyBalance(), $member->loyaltyLifetimePoints() and $member->loyaltyTier()
are plain read methods — safe to call anywhere, including outside the panel.
v1 limitation: a rule only ever earns once per source row — editing a paid order's shipping address later won't double-award, but there's no "re-earn when the points amount changes" support yet either.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security Vulnerabilities
Please review our security policy on how to report security vulnerabilities.
Credits
License
The MIT License (MIT). Please see License File for more information.