PHPackages                             offload-project/laravel-notification-preferences - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Mail &amp; Notifications](/categories/mail)
4. /
5. offload-project/laravel-notification-preferences

ActiveLibrary[Mail &amp; Notifications](/categories/mail)

offload-project/laravel-notification-preferences
================================================

Manage and display user notification preferences in Laravel

v1.1.0(4mo ago)7932↓50%1MITPHPPHP ^8.3CI passing

Since Dec 16Pushed 1mo agoCompare

[ Source](https://github.com/offload-project/laravel-notification-preferences)[ Packagist](https://packagist.org/packages/offload-project/laravel-notification-preferences)[ Docs](https://github.com/offload-project/laravel-notification-preferences)[ RSS](/packages/offload-project-laravel-notification-preferences/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (5)Dependencies (10)Versions (3)Used By (1)

 [![Latest Version on Packagist](https://camo.githubusercontent.com/6968bf7d47a845458128e04d5cb93cdae3afe3ff96290770c5e5d80f5c38d9c6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f66666c6f61642d70726f6a6563742f6c61726176656c2d6e6f74696669636174696f6e2d707265666572656e6365732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/offload-project/laravel-notification-preferences) [![GitHub Tests Action Status](https://camo.githubusercontent.com/de28dcd5a5dca838fec9049ec4be180b7b67bcaae5a32b28c3bc752db4a0082a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6f66666c6f61642d70726f6a6563742f6c61726176656c2d6e6f74696669636174696f6e2d707265666572656e6365732f74657374732e796d6c3f6272616e63683d6d61696e267374796c653d666c61742d737175617265)](https://github.com/offload-project/laravel-notification-preferences/actions) [![Total Downloads](https://camo.githubusercontent.com/c50a193e8984c474774ec76fd3de79565b02d2bd7b2ca5b5d8fef7d53b7c72aa/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6f66666c6f61642d70726f6a6563742f6c61726176656c2d6e6f74696669636174696f6e2d707265666572656e6365732e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/offload-project/laravel-notification-preferences)

Laravel Notification Preferences
================================

[](#laravel-notification-preferences)

A Laravel package for managing user notification preferences with support for multiple channels, notification groups, and automatic channel filtering — perfect for building notification settings UIs.

Features
--------

[](#features)

- **Automatic Filtering** — All notifications respect user preferences without code changes
- **Multiple Channels** — Support for mail, database, broadcast, SMS, or custom channels
- **Notification Grouping** — Organize notifications into logical groups (system, marketing, etc.)
- **Forced Channels** — Critical notifications that users cannot disable
- **Bulk Operations** — Disable all emails, mute a group, or toggle notification types
- **Structured Output** — UI-ready table structure for building preference pages
- **Opt-in/Opt-out Defaults** — Configure default behavior at global, group, or notification level
- **Event Dispatching** — Listen for preference changes for audit logging or sync
- **Email Unsubscribe Links** — Signed URLs for one-click unsubscribe with `List-Unsubscribe` header support
- **Input Validation** — Prevents setting preferences for unregistered notifications/channels

Requirements
------------

[](#requirements)

- PHP 8.3+
- Laravel 11/12/13

Installation
------------

[](#installation)

```
composer require offload-project/laravel-notification-preferences
```

Publish the config and migrations:

```
php artisan vendor:publish --tag=notification-preferences-config
php artisan vendor:publish --tag=notification-preferences-migrations
php artisan migrate
```

Quick Start
-----------

[](#quick-start)

**1. Add the trait to your User model:**

```
use OffloadProject\NotificationPreferences\Concerns\HasNotificationPreferences;

class User extends Authenticatable
{
    use HasNotificationPreferences;
}
```

**2. Register your notifications** in `config/notification-preferences.php`:

```
return [
    'channels' => [
        'mail' => ['label' => 'Email', 'enabled' => true],
        'database' => ['label' => 'In-App', 'enabled' => true],
    ],

    'groups' => [
        'system' => [
            'label' => 'System Notifications',
            'description' => 'Important system updates',
            'default_preference' => 'opt_in',
            'order' => 1,
        ],
    ],

    'notifications' => [
        \App\Notifications\OrderShipped::class => [
            'group' => 'system',
            'label' => 'Order Shipped',
            'description' => 'When your order ships',
            'order' => 1,
        ],
    ],
];
```

**3. Send notifications normally** — preferences are applied automatically:

```
$user->notify(new OrderShipped($order));
```

Managing Preferences
--------------------

[](#managing-preferences)

```
// Set a preference
$user->setNotificationPreference(OrderShipped::class, 'mail', false);

// Check a preference
$enabled = $user->getNotificationPreference(OrderShipped::class, 'mail');

// Get all preferences
$preferences = $user->getNotificationPreferences();

// Get structured table for UI
$table = $user->getNotificationPreferencesTable();
```

Bulk Operations
---------------

[](#bulk-operations)

Convenient methods for "disable all emails" or "mute marketing" features:

```
// Disable all emails
$user->setChannelPreferences('mail', false);

// Mute all marketing notifications for email
$user->setGroupPreferences('marketing', 'mail', false);

// Disable all channels for a notification type
$user->setNotificationChannelPreferences(OrderShipped::class, false);

// Reset all preferences to defaults
$user->resetNotificationPreferences();
```

All bulk methods return the count of updated preferences and automatically skip forced channels.

Explicit Control with Trait
---------------------------

[](#explicit-control-with-trait)

For granular control, use the `ChecksNotificationPreferences` trait in your notification:

```
use OffloadProject\NotificationPreferences\Concerns\ChecksNotificationPreferences;

class OrderShipped extends Notification
{
    use ChecksNotificationPreferences;

    public function via($notifiable)
    {
        return $this->allowedChannels($notifiable, ['mail', 'database', 'broadcast']);
    }
}
```

Forced Channels
---------------

[](#forced-channels)

Prevent users from disabling critical notifications:

```
'notifications' => [
    SecurityAlert::class => [
        'group' => 'security',
        'label' => 'Security Alerts',
        'force_channels' => ['mail', 'database'],
    ],
],
```

Per-Channel Defaults
--------------------

[](#per-channel-defaults)

Set specific channels enabled by default:

```
'notifications' => [
    OrderShipped::class => [
        'group' => 'system',
        'label' => 'Order Shipped',
        'default_channels' => ['mail', 'database'], // Only these enabled by default
    ],
],
```

Events
------

[](#events)

The package dispatches events when preferences change:

```
use OffloadProject\NotificationPreferences\Events\NotificationPreferenceChanged;

Event::listen(NotificationPreferenceChanged::class, function ($event) {
    // $event->preference - The NotificationPreference model
    // $event->user - The user who changed the preference
    // $event->wasCreated - Whether this was a new preference or update
});
```

Using the Facade
----------------

[](#using-the-facade)

For quick access without dependency injection:

```
use OffloadProject\NotificationPreferences\Facades\NotificationPreferences;

// Check if a channel is enabled
NotificationPreferences::isChannelEnabled($user, OrderShipped::class, 'mail');

// Set a preference
NotificationPreferences::setPreference($user, OrderShipped::class, 'mail', false);

// Get structured table for UI
NotificationPreferences::getPreferencesTable($user);

// Discover registered configuration
NotificationPreferences::getRegisteredChannels();    // ['mail', 'database']
NotificationPreferences::getRegisteredGroups();      // ['system', 'marketing']
NotificationPreferences::getRegisteredNotifications(); // [OrderShipped::class, ...]
```

Using the Interface
-------------------

[](#using-the-interface)

For dependency injection and testing, use the interface:

```
use OffloadProject\NotificationPreferences\Contracts\NotificationPreferenceManagerInterface;

class NotificationPreferenceController
{
    public function __construct(
        private NotificationPreferenceManagerInterface $manager
    ) {}

    public function update(Request $request)
    {
        $this->manager->setPreference(
            $request->user(),
            $request->notification_type,
            $request->channel,
            $request->enabled
        );
    }
}
```

Table Structure Output
----------------------

[](#table-structure-output)

The `getNotificationPreferencesTable()` method returns UI-ready data:

```
[
    [
        'group' => 'system',
        'label' => 'System Notifications',
        'description' => 'Important system updates',
        'notifications' => [
            [
                'type' => 'App\Notifications\OrderShipped',
                'label' => 'Order Shipped',
                'description' => 'When your order ships',
                'channels' => [
                    'mail' => ['enabled' => true, 'forced' => false],
                    'database' => ['enabled' => true, 'forced' => false],
                ],
            ],
        ],
    ],
]
```

Inertia.js Integration
----------------------

[](#inertiajs-integration)

Share preferences via middleware:

```
// app/Http/Middleware/HandleInertiaRequests.php
public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'notificationPreferences' => fn () => $request->user()?->getNotificationPreferencesTable(),
    ];
}
```

Cache Management
----------------

[](#cache-management)

Preferences are cached for performance (default: 24 hours). Configure the TTL in your config:

```
// config/notification-preferences.php
'cache_ttl' => 1440, // minutes (default: 24 hours)
```

Clear caches when needed:

```
use OffloadProject\NotificationPreferences\Contracts\NotificationPreferenceManagerInterface;

$manager = app(NotificationPreferenceManagerInterface::class);

// Clear all cached preferences for a user
$manager->clearUserCache($userId);

// Clear the memoized config cache (useful after runtime config changes)
$manager->clearConfigCache();
```

Exception Handling
------------------

[](#exception-handling)

The package validates all inputs and throws specific exceptions with helpful messages:

```
use OffloadProject\NotificationPreferences\Exceptions\InvalidNotificationTypeException;
use OffloadProject\NotificationPreferences\Exceptions\InvalidChannelException;
use OffloadProject\NotificationPreferences\Exceptions\InvalidGroupException;

try {
    $user->setNotificationPreference('UnregisteredNotification', 'mail', false);
} catch (InvalidNotificationTypeException $e) {
    // "Notification type 'UnregisteredNotification' is not registered...
    //  Add it to the 'notifications' array in 'config/notification-preferences.php'."
}

try {
    $user->setNotificationPreference(OrderShipped::class, 'sms', false);
} catch (InvalidChannelException $e) {
    // "Channel 'sms' is not registered... Available channels: mail, database."
}

try {
    $user->setGroupPreferences('nonexistent', 'mail', false);
} catch (InvalidGroupException $e) {
    // "Group 'nonexistent' is not registered... Available groups: system, marketing."
}
```

Email Unsubscribe Links
-----------------------

[](#email-unsubscribe-links)

The package can generate signed URLs that let users unsubscribe directly from emails — no login required. It also supports `List-Unsubscribe` headers for native unsubscribe buttons in Gmail, Apple Mail, and other clients (RFC 8058).

### Adding Unsubscribe Links to Notifications

[](#adding-unsubscribe-links-to-notifications)

Use the `HasUnsubscribeUrl` trait on your notification class:

```
use OffloadProject\NotificationPreferences\Concerns\HasUnsubscribeUrl;

class OrderShipped extends Notification
{
    use HasUnsubscribeUrl;

    public function toMail($notifiable): MailMessage
    {
        return $this->withUnsubscribeHeaders(
            (new MailMessage)
                ->line('Your order has shipped!')
                ->action('Unsubscribe', $this->getUnsubscribeUrl($notifiable)),
            $notifiable
        );
    }
}
```

`withUnsubscribeHeaders()` adds `List-Unsubscribe` and `List-Unsubscribe-Post` headers so email clients can show native unsubscribe buttons. The `getUnsubscribeUrl()` method generates a signed URL you can place anywhere in the email body.

### Generating URLs Directly

[](#generating-urls-directly)

You can also generate URLs from the user model or facade:

```
// From the user model
$url = $user->notificationUnsubscribeUrl(OrderShipped::class);
$url = $user->notificationResubscribeUrl(OrderShipped::class);

// From the facade
use OffloadProject\NotificationPreferences\Facades\NotificationPreferences;

$url = NotificationPreferences::unsubscribeUrl($user, OrderShipped::class);
$url = NotificationPreferences::resubscribeUrl($user, OrderShipped::class);

// For a specific channel (defaults to 'mail')
$url = NotificationPreferences::unsubscribeUrl($user, OrderShipped::class, 'database');
```

### How It Works

[](#how-it-works)

When a user clicks the unsubscribe link, the package:

1. Validates the signed URL (tamper-proof, no auth required)
2. Disables the notification type for that channel
3. Returns a JSON response, or redirects to your configured URL

The POST method is also supported for RFC 8058 one-click unsubscribe from email clients.

### Configuration

[](#configuration)

```
// config/notification-preferences.php
'unsubscribe' => [
    // Whether to register unsubscribe routes
    'enabled' => true,

    // Route prefix for unsubscribe/resubscribe endpoints
    'route_prefix' => 'notification-preferences',

    // Middleware for the unsubscribe routes
    'middleware' => ['web'],

    // Signed URL TTL in minutes (null for permanent/non-expiring)
    'url_ttl' => null,

    // Redirect to this URL after unsubscribing (with status, notification_type, and channel query params)
    // Set to null to return a JSON response instead
    'redirect_url' => null,

    // Enable resubscribe functionality
    'resubscribe_enabled' => true,
],
```

### Redirect Example

[](#redirect-example)

When `redirect_url` is set, users are redirected after unsubscribing:

```
'redirect_url' => '/notification-settings',
// Redirects to: /notification-settings?status=unsubscribed&notification_type=App\Notifications\OrderShipped&channel=mail
```

This lets you handle the confirmation page in your own frontend (Blade, Inertia, Livewire, etc.).

Uninstalling
------------

[](#uninstalling)

```
php artisan notification-preferences:uninstall --force
composer remove offload-project/laravel-notification-preferences
rm config/notification-preferences.php
```

Configuration Reference
-----------------------

[](#configuration-reference)

### Global Options

[](#global-options)

OptionTypeDescription`default_preference`string`opt_in` or `opt_out` for all notifications`cache_ttl`intCache duration in minutes (default: 1440 = 24h)`table_name`stringDatabase table name (default: notification\_preferences)`user_model`stringUser model class (default: App\\Models\\User)### Channels

[](#channels)

OptionTypeDescription`label`stringDisplay name for UI`enabled`boolWhether channel is available (default: true)### Groups

[](#groups)

OptionTypeDescription`label`stringDisplay name for UI`description`stringOptional description for UI`default_preference`string`opt_in` or `opt_out` (overrides global)`order`intSort order in UI### Notifications

[](#notifications)

OptionTypeDescription`group`stringGroup key this notification belongs to`label`stringDisplay name for UI`description`stringOptional description for UI`default_preference`string`opt_in` or `opt_out` (overrides group)`default_channels`arraySpecific channels enabled by default`force_channels`arrayChannels that cannot be disabled`order`intSort order within group### Unsubscribe

[](#unsubscribe)

OptionTypeDescription`enabled`boolRegister unsubscribe routes (default: true)`route_prefix`stringURL prefix for routes (default: notification-preferences)`middleware`arrayMiddleware stack for routes (default: \['web'\])`url_ttl`int|nullSigned URL expiration in minutes (null = permanent)`redirect_url`string|nullRedirect after action, or null for JSON response`resubscribe_enabled`boolRegister resubscribe route (default: true)Testing
-------

[](#testing)

```
./vendor/bin/pest
```

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE) for more information.

###  Health Score

45

—

FairBetter than 93% of packages

Maintenance84

Actively maintained with recent releases

Popularity24

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 76.2% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~22 days

Total

2

Last Release

131d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/331bcc01f75f46dded875d74f3db055da6d74be5f8820f0a29105c6a2cd8afc8?d=identicon)[shavonn](/maintainers/shavonn)

---

Top Contributors

[![shavonn](https://avatars.githubusercontent.com/u/3074595?v=4)](https://github.com/shavonn "shavonn (16 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (5 commits)")

---

Tags

laravelnotificationpreferences

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/offload-project-laravel-notification-preferences/health.svg)

```
[![Health](https://phpackages.com/badges/offload-project-laravel-notification-preferences/health.svg)](https://phpackages.com/packages/offload-project-laravel-notification-preferences)
```

###  Alternatives

[laravel-notification-channels/telegram

Telegram Notifications Channel for Laravel

1.1k3.4M35](/packages/laravel-notification-channels-telegram)[laravel-notification-channels/twilio

Provides Twilio notification channel for Laravel

2587.7M12](/packages/laravel-notification-channels-twilio)[laravel-notification-channels/discord

Laravel notification driver for Discord.

2371.3M11](/packages/laravel-notification-channels-discord)[s-ichikawa/laravel-sendgrid-driver

This library adds a 'sendgrid' mail driver to Laravel.

4139.3M1](/packages/s-ichikawa-laravel-sendgrid-driver)[yadahan/laravel-authentication-log

Laravel Authentication Log provides authentication logger and notification for Laravel.

416632.8k5](/packages/yadahan-laravel-authentication-log)[ghanem/laravel-smsmisr

Send SMS and SMS Notification via SMS Misr for Laravel

194.8k](/packages/ghanem-laravel-smsmisr)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
