scabarcas / laravel-notify-matrix
Manage per-user notification preferences in Laravel, with channel-level opt-in and opt-out.
Package info
github.com/scabarcas17/laravel-notify-matrix
pkg:composer/scabarcas/laravel-notify-matrix
Requires
- php: ^8.3
- illuminate/contracts: ^11.0|^12.0|^13.0
- illuminate/database: ^11.0|^12.0|^13.0
- illuminate/notifications: ^11.0|^12.0|^13.0
- illuminate/support: ^11.0|^12.0|^13.0
Requires (Dev)
- larastan/larastan: ^3
- laravel/framework: ^12.0|^13.0
- laravel/pint: 1.29.1
- orchestra/testbench: ^10.11|^11.0
- pestphp/pest: ^4.4
- phpstan/phpstan: ^2.1
README
Manage per-user notification preferences in Laravel. Each user can opt in or out of channels for each notification group.
Installation
composer require scabarcas/laravel-notify-matrix
php artisan vendor:publish --tag=notify-matrix-config php artisan vendor:publish --tag=notify-matrix-migrations php artisan migrate
Quick start
Add the trait to the user model (or any notifiable):
use Illuminate\Foundation\Auth\User as Authenticatable; use Scabarcas\LaravelNotifyMatrix\Concerns\HasNotificationPreferences; class User extends Authenticatable { use HasNotificationPreferences; }
Tag each notification with the group it belongs to:
use Illuminate\Notifications\Notification; use Scabarcas\LaravelNotifyMatrix\Attributes\NotificationGroup; #[NotificationGroup('orders')] class OrderShipped extends Notification { public function via($notifiable): array { return ['mail', 'database']; } }
Read and write preferences from the model:
$user->wants('orders', 'mail'); // true | false $user->wants(OrderShipped::class, 'mail'); // resolves the group via the attribute $user->setPreference('orders', 'mail', false); $user->enable('orders', 'mail'); $user->disable('orders', 'mail'); $user->getPreferences(); $user->getPreferencesForGroup('orders'); $user->clearPreferences('orders');
With the trait on the notifiable and the attribute on the notification, the dispatch listener filters channels according to stored preferences. Forced channels are always delivered.
Configuration
return [ 'table' => 'notification_preferences', // Applied when no preference exists for a channel within a group. // Each group may override this below. Supported: "opt_in", "opt_out". 'default_policy' => 'opt_in', 'groups' => [ 'marketing' => [ 'default_policy' => 'opt_out', ], 'security' => [ 'default_policy' => 'opt_in', 'forced' => ['mail'], ], ], 'class_map' => [ // Map notifications that cannot be annotated directly. // \Vendor\Pkg\Notifications\InvoicePaid::class => 'billing', ], 'cache' => [ 'enabled' => true, 'ttl' => 300, 'store' => null, ], ];
Forced channels
Channels listed under groups.<group>.forced are delivered even when the user has opted out. Common use cases are security alerts and account verification messages.
Class map
Third-party notification classes that cannot be annotated with #[NotificationGroup] can be mapped to a group through the class_map entry. Annotated classes always take precedence over the map.
How it works
The package registers a listener for Illuminate\Notifications\Events\NotificationSending that runs before each channel dispatch:
- If the notifiable does not use
HasNotificationPreferences, the listener does not interfere. - If the notification has neither a
#[NotificationGroup]attribute nor aclass_mapentry, the listener does not interfere. - If the channel is listed as forced for the group, the channel is delivered.
- If the user has a stored preference for the channel, that value decides.
- Otherwise, the group default policy decides (or the global default if the group has none).
Testing
composer install
composer test
composer analyse
composer format
Author
Sebastian Cabarcas Berrio · sebastianberrio45@hotmail.com · @scabarcas17
License
MIT © Sebastian Cabarcas Berrio