PHPackages                             sysmatter/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. sysmatter/laravel-notification-preferences

Abandoned → [offload-project/laravel-notification-preferences](/?search=offload-project%2Flaravel-notification-preferences)Library[Mail &amp; Notifications](/categories/mail)

sysmatter/laravel-notification-preferences
==========================================

Manage and display user notification preferences in Laravel

v2.0.0(4mo ago)0139MITPHPCI passing

Since Oct 15Pushed 4mo agoCompare

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

READMEChangelog (7)Dependencies (10)Versions (10)Used By (0)

Laravel Notification Preferences Packaged Moved -&gt; [offload-project/laravel-notification-preferences](https://github.com/offload-project/laravel-notification-preferences)
=============================================================================================================================================================================

[](#laravel-notification-preferences-packaged-moved---offload-projectlaravel-notification-preferences)

A Laravel package for managing user notification preferences with support for multiple channels, notification groups, and a structured table output for display.

Features
--------

[](#features)

- ✅ User-specific notification preferences per channel
- ✅ Automatic channel filtering for all notifications
- ✅ Opt-in trait for granular control
- ✅ Notification grouping for organization
- ✅ Configurable default behaviors (opt-in/opt-out)
- ✅ Forced channels that cannot be disabled
- ✅ Structured table output for UI rendering
- ✅ Laravel 12 compatible
- ✅ PostgreSQL 18 support

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

[](#requirements)

- PHP 8.2+
- Laravel 11+
- Any Laravel-supported database (PostgreSQL, MySQL, SQLite, etc.)

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

[](#installation)

```
composer require sysmatter/laravel-notification-preferences
```

Publish the config and migrations:

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

Run migrations:

```
php artisan migrate
```

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

[](#uninstalling)

To completely remove the package and its data:

```
# This will drop the notification_preferences table
php artisan notification-preferences:uninstall

# Or force without confirmation
php artisan notification-preferences:uninstall --force

# Then remove from composer
composer remove sysmatter/laravel-notification-preferences

# Optionally remove published config
rm config/notification-preferences.php
```

**Warning:** The uninstall command permanently deletes all notification preferences. Make sure to back up your data if needed.

Configuration
-------------

[](#configuration)

Edit `config/notification-preferences.php`:

```
return [
    // Define available channels
    'channels' => [
        'mail' => ['label' => 'Email', 'enabled' => true],
        'database' => ['label' => 'In-App', 'enabled' => true],
        'broadcast' => ['label' => 'Push', 'enabled' => true],
        'sms' => ['label' => 'SMS', 'enabled' => true],
    ],

    // Global default: 'opt_in' or 'opt_out'
    'default_preference' => 'opt_in',

    // Define notification groups
    'groups' => [
        'system' => [
            'label' => 'System Notifications',
            'description' => 'Important system updates',
            'default_preference' => 'opt_in',
            'order' => 1,
        ],
        'marketing' => [
            'label' => 'Marketing',
            'description' => 'Promotional content',
            'default_preference' => 'opt_out',
            'order' => 2,
        ],
    ],

    // Register your notifications
    'notifications' => [
        \App\Notifications\OrderShipped::class => [
            'group' => 'system',
            'label' => 'Order Shipped',
            'description' => 'Notification when your order ships',
            'default_preference' => 'opt_in',
            'default_channels' => ['mail', 'database'],
            'force_channels' => [], // Channels that can't be disabled
            'order' => 1,
        ],
        \App\Notifications\WeeklyNewsletter::class => [
            'group' => 'marketing',
            'label' => 'Weekly Newsletter',
            'description' => 'Our weekly email digest',
            'default_channels' => ['mail'],
            'order' => 2,
        ],
    ],
];
```

Usage
-----

[](#usage)

### Add Trait to User Model

[](#add-trait-to-user-model)

```
use SysMatter\NotificationPreferences\Concerns\HasNotificationPreferences;

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

### Option A: Automatic Filtering (Recommended)

[](#option-a-automatic-filtering-recommended)

All registered notifications in the config will automatically filter channels based on user preferences. No changes needed to your notification classes!

```
// This notification will automatically respect user preferences
$user->notify(new OrderShipped($order));
```

### Option B: Explicit Control with Trait

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

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

```
use SysMatter\NotificationPreferences\Concerns\ChecksNotificationPreferences;
use Illuminate\Notifications\Notification;

class OrderShipped extends Notification
{
    use ChecksNotificationPreferences;

    public function via($notifiable)
    {
        // Define all possible channels, preferences will filter them
        return $this->allowedChannels($notifiable, ['mail', 'database', 'broadcast']);
    }

    public function toMail($notifiable)
    {
        // ...
    }
}
```

### Managing Preferences

[](#managing-preferences)

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

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

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

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

Bulk Update Operations
----------------------

[](#bulk-update-operations)

The package provides convenient methods for bulk updating notification preferences, making it easy to implement "disable all emails" or "turn off marketing" features.

### Available Bulk Methods

[](#available-bulk-methods)

#### Disable/Enable All Notifications in a Group for a Channel

[](#disableenable-all-notifications-in-a-group-for-a-channel)

Turn off all marketing emails:

```
$user->setGroupChannelPreference('marketing', 'mail', false);
```

Turn on all system notifications for in-app:

```
$user->setGroupChannelPreference('system', 'database', true);
```

#### Disable/Enable a Channel Across All Notifications

[](#disableenable-a-channel-across-all-notifications)

Turn off all email notifications:

```
$user->setChannelPreferenceForAll('mail', false);
```

Enable push notifications for everything:

```
$user->setChannelPreferenceForAll('broadcast', true);
```

#### Disable/Enable All Channels for a Notification Type

[](#disableenable-all-channels-for-a-notification-type)

Turn off all channels for a specific notification:

```
$user->setAllChannelsForNotification(OrderShipped::class, false);
```

Enable all channels for security alerts:

```
$user->setAllChannelsForNotification(SecurityAlert::class, true);
```

### Return Values

[](#return-values)

All bulk methods return the **count of preferences updated**:

```
$count = $user->setChannelPreferenceForAll('mail', false);
// Returns: 15 (updated 15 notification preferences)
```

This is useful for providing user feedback:

```
$count = $user->setGroupChannelPreference('marketing', 'mail', false);

return response()->json([
    'message' => "Disabled email for {$count} marketing notifications"
]);
```

### Forced Channels are Skipped

[](#forced-channels-are-skipped)

Bulk operations automatically skip forced channels:

```
'notifications' => [
    SecurityAlert::class => [
        'group' => 'security',
        'label' => 'Security Alerts',
        'force_channels' => ['mail'], // Always send emails
    ],
],
```

```
// This will NOT disable email for SecurityAlert
$user->setChannelPreferenceForAll('mail', false);
```

### UI Implementation Examples

[](#ui-implementation-examples)

#### "Disable All Emails" Button

[](#disable-all-emails-button)

```
public function disableAllEmails(Request $request)
{
    $count = $request->user()->setChannelPreferenceForAll('mail', false);

    return back()->with('success', "Disabled email notifications for {$count} notification types");
}
```

#### "Mute Marketing" Toggle

[](#mute-marketing-toggle)

```
public function toggleMarketing(Request $request)
{
    $enabled = $request->boolean('enabled');
    $count = $request->user()->setGroupChannelPreference('marketing', 'mail', $enabled);

    $action = $enabled ? 'enabled' : 'disabled';

    return back()->with('success', "Marketing emails {$action}");
}
```

#### "Notification Type Master Toggle"

[](#notification-type-master-toggle)

```
public function toggleNotificationType(Request $request, string $notificationType)
{
    $enabled = $request->boolean('enabled');
    $count = $request->user()->setAllChannelsForNotification($notificationType, $enabled);

    return response()->json([
        'updated' => $count,
        'enabled' => $enabled
    ]);
}
```

### Direct Manager Access

[](#direct-manager-access)

You can also use the manager directly:

```
use SysMatter\NotificationPreferences\NotificationPreferenceManager;

$manager = app(NotificationPreferenceManager::class);

// Same methods available
$count = $manager->setGroupPreference($user, 'marketing', 'mail', false);
$count = $manager->setChannelPreference($user, 'mail', false);
$count = $manager->setNotificationPreference($user, OrderShipped::class, false);
```

### Building a Preferences UI

[](#building-a-preferences-ui)

Here's a complete example of a preferences page controller:

```
public function index(Request $request)
{
    $user = $request->user();

    return view('preferences.notifications', [
        'preferences' => $user->getNotificationPreferencesTable(),
        'channels' => config('notification-preferences.channels'),
    ]);
}

public function update(Request $request)
{
    $user = $request->user();

    $validated = $request->validate([
        'action' => 'required|in:single,group,channel,notification',
        'notification_type' => 'required_if:action,single,notification',
        'channel' => 'required_if:action,single,group,channel',
        'group' => 'required_if:action,group',
        'enabled' => 'required|boolean',
    ]);

    $count = match($validated['action']) {
        'single' => $user->setNotificationPreference(
            $validated['notification_type'],
            $validated['channel'],
            $validated['enabled']
        ) ? 1 : 0,

        'group' => $user->setGroupChannelPreference(
            $validated['group'],
            $validated['channel'],
            $validated['enabled']
        ),

        'channel' => $user->setChannelPreferenceForAll(
            $validated['channel'],
            $validated['enabled']
        ),

        'notification' => $user->setAllChannelsForNotification(
            $validated['notification_type'],
            $validated['enabled']
        ),
    };

    return response()->json([
        'success' => true,
        'count' => $count,
    ]);
}
```

### Table Structure Output

[](#table-structure-output)

The `getNotificationPreferencesTable()` method returns data structured for easy UI rendering:

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

API Endpoints Example
---------------------

[](#api-endpoints-example)

Create a controller to manage preferences:

```
use SysMatter\NotificationPreferences\NotificationPreferenceManager;

class NotificationPreferenceController extends Controller
{
    public function index(Request $request)
    {
        return inertia('Settings/Notifications', [
            'preferences' => $request->user()->getNotificationPreferencesTable(),
        ]);
    }

    public function update(Request $request, NotificationPreferenceManager $manager)
    {
        $validated = $request->validate([
            'notification_type' => 'required|string',
            'channel' => 'required|string',
            'enabled' => 'required|boolean',
        ]);

        $manager->setPreference(
            $request->user(),
            $validated['notification_type'],
            $validated['channel'],
            $validated['enabled']
        );

        return back();
    }
}
```

Routes:

```
Route::middleware(['auth'])->group(function () {
    Route::get('/settings/notifications', [NotificationPreferenceController::class, 'index']);
    Route::put('/settings/notifications', [NotificationPreferenceController::class, 'update']);
});
```

Frontend Example (Inertia/React 19)
-----------------------------------

[](#frontend-example-inertiareact-19)

```
import {useForm} from '@inertiajs/react';
import {useState} from 'react';

interface Channel {
    enabled: boolean;
    forced: boolean;
}

interface Notification {
    type: string;
    label: string;
    description: string | null;
    channels: Record;
}

interface Group {
    group: string;
    label: string;
    description: string | null;
    notifications: Notification[];
}

interface Props {
    preferences: Group[];
}

export default function NotificationPreferences({preferences}: Props) {
    const [channels] = useState(() => {
        // Extract channel names from first notification
        if (preferences.length > 0 && preferences[0].notifications.length > 0) {
            return Object.keys(preferences[0].notifications[0].channels);
        }
        return [];
    });

    const handleToggle = (notificationType: string, channel: string, currentValue: boolean) => {
        router.put(
            notificationPreferenceController.update.url(),
            {
                notification_type: notificationType,
                channel,
                enabled: !currentValue,
            },
            {
                preserveScroll: true,
            },
        );
    };

    return (

                Notification Preferences

                    Manage how you receive notifications

            {preferences.map((group) => (

                        {group.label}
                        {group.description && (
                            {group.description}
                        )}

                                    Notification

                                {channels.map((channel) => (

                                        {channel.charAt(0).toUpperCase() + channel.slice(1)}

                                ))}

                            {group.notifications.map((notification) => (

                                                {notification.label}

                                            {notification.description && (

                                                    {notification.description}

                                            )}

                                    {channels.map((channelKey) => {
                                        const channel = notification.channels[channelKey];
                                        return (

                                                        !channel.forced &&
                                                        handleToggle(
                                                            notification.type,
                                                            channelKey,
                                                            channel.enabled
                                                        )
                                                    }
                                                    disabled={channel.forced}
                                                    className={`
                              relative inline-flex h-6 w-11 items-center rounded-full
                              transition-colors focus:outline-none focus:ring-2
                              focus:ring-blue-500 focus:ring-offset-2
                              ${
                                                        channel.enabled
                                                            ? 'bg-blue-600'
                                                            : 'bg-gray-200'
                                                    }
                              ${
                                                        channel.forced
                                                            ? 'opacity-50 cursor-not-allowed'
                                                            : 'cursor-pointer'
                                                    }
                            `}
                                                    title={
                                                        channel.forced
                                                            ? 'This notification cannot be disabled'
                                                            : undefined
                                                    }
                                                >

                                        );
                                    })}

                            ))}

            ))}

    );
}
```

Testing with Pest
-----------------

[](#testing-with-pest)

```
use SysMatter\NotificationPreferences\Models\NotificationPreference;
use App\Models\User;
use App\Notifications\OrderShipped;

it('filters channels based on user preferences', function () {
    $user = User::factory()->create();

    $user->setNotificationPreference(OrderShipped::class, 'mail', false);

    expect($user->getNotificationPreference(OrderShipped::class, 'mail'))
        ->toBeFalse();
});

it('returns structured table data', function () {
    $user = User::factory()->create();

    $table = $user->getNotificationPreferencesTable();

    expect($table)
        ->toBeArray()
        ->and($table[0])->toHaveKeys(['group', 'label', 'notifications'])
        ->and($table[0]['notifications'][0])->toHaveKeys(['type', 'label', 'channels']);
});
```

Testing with Pest
-----------------

[](#testing-with-pest-1)

The package includes a comprehensive test suite using Pest.

```
# Run all tests
composer test

# Run with coverage
composer test-coverage

# Run PHPStan analysis
composer analyse
```

### Test Structure

[](#test-structure)

- **Unit Tests**: Test individual components in isolation

    - `NotificationPreferenceTest.php` - Model tests
    - `NotificationPreferenceManagerTest.php` - Manager logic tests
    - `PreferencesTableTest.php` - Table structure tests
    - `HasNotificationPreferencesTest.php` - Trait tests
- **Feature Tests**: Test integrated behavior

    - `NotificationFilteringTest.php` - Channel filtering tests
    - `DefaultPreferencesTest.php` - Default behavior tests

### Example Test

[](#example-test)

```
use SysMatter\NotificationPreferences\Models\NotificationPreference;
use App\Models\User;
use App\Notifications\OrderShipped;

it('filters channels based on user preferences', function () {
    $user = User::factory()->create();

    $user->setNotificationPreference(OrderShipped::class, 'mail', false);

    expect($user->getNotificationPreference(OrderShipped::class, 'mail'))
        ->toBeFalse();
});
```

Advanced Features
-----------------

[](#advanced-features)

### Forced Channels

[](#forced-channels)

Prevent users from disabling critical notifications on certain channels:

```
'notifications' => [
    \App\Notifications\SecurityAlert::class => [
        'group' => 'system',
        'label' => 'Security Alerts',
        'force_channels' => ['mail', 'database'], // Can't be disabled
    ],
],
```

### Per-Channel Defaults

[](#per-channel-defaults)

Set different defaults for each channel:

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

### Cache Management

[](#cache-management)

The package caches preferences for performance. Clear cache when needed:

```
use SysMatter\NotificationPreferences\NotificationPreferenceManager;

$manager = app(NotificationPreferenceManager::class);
$manager->clearUserCache($userId);
```

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

Security
--------

[](#security)

Please review [our security policy](SECURITY.md) for reporting vulnerabilities.

Credits
-------

[](#credits)

- [Shavonn Brown](https://github.com/sysmatter)

License
-------

[](#license)

MIT License. See [LICENSE](LICENSE.md) file for details.

###  Health Score

35

—

LowBetter than 80% of packages

Maintenance74

Regular maintenance activity

Popularity10

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity41

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 77.8% 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 ~10 days

Total

7

Last Release

145d ago

Major Versions

v1.2.1 → v2.0.02025-12-16

### Community

Maintainers

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

---

Top Contributors

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

---

Tags

laravelnotificationpreferences

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

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

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

PHPackages © 2026

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