Loyalty plugin screenshot
Dark mode ready
Multilingual support
Supports v5.x

Loyalty by Elvin Qulizada

Community

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.

Tags: Action Panels
Supported versions:
5.x
Third-party plugin. This is built by the community, not the Filament team. Filament does not review, endorse, or vet the security of plugins outside the filament/ namespace. Review the source and install at your own risk. Found malware or an unresolved security issue the author won't address? Report it .
Elvin Qulizada avatar Author: Elvin Qulizada

Documentation

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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

  1. 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;
}
  1. 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.

  1. 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.

  1. 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,
        ]);
}
  1. 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.

The author

Elvin Qulizada avatar Author: Elvin Qulizada

Full-stack developer specializing in Laravel, Filament. I build modern web applications, SaaS platforms, and developer tools, with a strong focus on clean architecture, reusable components, and great developer experience. I enjoy contributing to the Filament ecosystem by creating plugins and tools that solve real-world development needs.

Plugins
3
Stars
4

From the same author