Skip to content

thedevdojo/notifications

Repository files navigation

DevDojo Notifications

Drop-in in-app database notifications and notification preferences for Laravel. Ships the notifications table used by Laravel's database notification channel, a notification_preferences column + trait for your User, sane preference defaults, and a mark-as-read endpoint. Front-end agnostic, so it fits any theme.

devdojo/notifications is one of the feature packages bundled by devdojo/foundation, but it works perfectly well standalone in any Laravel app.


Table of contents


Requirements

Requirement Notes
PHP ^8.2
Laravel ^10 / ^11 / ^12

The package is front-end agnostic: it provides the storage, preferences, and endpoint, and leaves the notification UI (bells, dropdowns, popups) to your application/theme.


How it works

┌──────────────────────────────────────────────────────────────┐
│ devdojo/notifications                                        │
│                                                              │
│  notifications table (Laravel database channel, UUID ids)    │
│  users.notification_preferences (json)                       │
│                                                              │
│  Trait added to your User:                                   │
│   • HasNotificationPreferences (notificationPreference())    │
│                                                              │
│  POST /notification/read/{id}  •  preference defaults config │
└──────────────────────────────────────────────────────────────┘
  • Notifications are plain Laravel database notifications — send them with $user->notify(...) and read them with $user->notifications / $user->unreadNotifications. The package provides the table.
  • Each user gets a notification_preferences JSON column; the HasNotificationPreferences trait reads it with per-key fallbacks to the package defaults. security_alerts can never be turned off.
  • POST /notification/read/{id} removes a notification for the signed-in user — the "dismiss" action for a notification list UI.

Installation

composer require devdojo/notifications

Publish the migrations and config, then migrate:

php artisan vendor:publish --tag=notifications:migrations
php artisan vendor:publish --tag=notifications:config
php artisan migrate

Migrations are publish-only (not auto-loaded) so the notifications table and the users.notification_preferences column live in your app's database/migrations and are yours to edit.

The preferences migration adds the column ->after('avatar') — if your users table has no avatar column, adjust the published migration before migrating.

Then wire your User model.


Configuration

Publishing notifications:config writes config/devdojo/notifications/settings.php (config key devdojo.notifications.settings):

return [
    'default_preferences' => [
        'email_notifications' => true,
        'marketing_emails' => true,
        'product_updates' => true,
        'blog_notifications' => false,
        'security_alerts' => true,
    ],
];

These are the values notificationPreference() falls back to when a user has not stored their own preferences yet.


Wiring your User model

Add the HasNotificationPreferences trait to your User model (alongside Laravel's own Notifiable):

use Devdojo\Notifications\Traits\HasNotificationPreferences;
use Illuminate\Notifications\Notifiable;

class User extends Authenticatable
{
    use HasNotificationPreferences, Notifiable;
}

The trait automatically casts notification_preferences to an array and adds:

$user->notificationPreference('marketing_emails');        // bool — stored value or default
$user->notificationPreference('custom_key', 'fallback');  // with an explicit fallback

Sending & reading notifications

Nothing package-specific here — use Laravel's database channel:

use Illuminate\Notifications\Notification;

class ProjectShipped extends Notification
{
    public function via(object $notifiable): array
    {
        return ['database'];
    }

    public function toDatabase(object $notifiable): array
    {
        return ['message' => 'Your project has shipped!'];
    }
}

$user->notify(new ProjectShipped);

$user->notifications;               // all notifications, newest first
$user->unreadNotifications;         // read_at is null
$user->unreadNotifications->markAsRead();

Respect a preference before sending:

if ($user->notificationPreference('product_updates')) {
    $user->notify(new ProductUpdate($release));
}

The mark-as-read endpoint

Method URI Name Middleware
POST /notification/read/{id} wave.notification.read web, auth

It looks the notification up on the signed-in user only and deletes it (this package treats "read" as "dismissed"). The response is JSON:

// e.g. when a notification is dismissed from a dropdown list
fetch(`/notification/read/${notification.id}`, {
    method: 'POST',
    headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}', 'Content-Type': 'application/json' },
    body: JSON.stringify({ listid: 'notification-7' }),
});
{ "type": "success", "message": "Marked Notification as Read", "listid": "notification-7" }

An unknown id — or an id belonging to another user — returns { "type": "error", "message": "Could not find the specified notification." }.


Notification preferences

Preferences are a JSON object on the user keyed by feature, e.g.:

$user->notification_preferences = [
    'email_notifications' => true,
    'marketing_emails' => false,
];
$user->save();

notificationPreference($key, $default = null) resolves in this order:

  1. security_alerts is always true — it cannot be disabled, even if stored as false.
  2. The user's stored value for $key, if present.
  3. The $default you pass, if any.
  4. The package default from devdojo.notifications.settings.default_preferences.

The keys are yours to define — store any keys your app needs and add matching defaults to the published config.


Using with DevDojo Foundation

When the devdojo/foundation metapackage is installed, notifications self-gate on the feature flag:

// config/foundation.php
'features' => [
    'notifications' => true,   // flip to false (or toggle at /foundation/setup) to disable
],

When notifications is disabled the notification/read/{id} route is not registered. The config, trait, and migrations remain available, so toggling is lossless.

Standalone (no Foundation present), the flag is absent and notifications default to on.


Configuration reference

config/devdojo/notifications/settings.php

return [
    'default_preferences' => [
        'email_notifications' => true,
        'marketing_emails' => true,
        'product_updates' => true,
        'blog_notifications' => false,
        'security_alerts' => true,   // always treated as enabled
    ],
];

Publish tags

Tag Publishes to
notifications:config config/devdojo/notifications/settings.php
notifications:migrations database/migrations

FAQ / troubleshooting

Marking as read deletes the notification — why? The endpoint powers dismissable in-app notification lists: once dismissed, the row is removed. If you want persistent read state instead, call markAsRead() on the notification (Laravel sets read_at) and skip the endpoint.

Where is the notification bell / dropdown UI? The package is headless on the front-end. Render $user->notifications however your app prefers; in the Wave starter kit the UI ships as theme partials you can edit.

Where does the notifications table come from? It's a publish-only migration — run php artisan vendor:publish --tag=notifications:migrations && php artisan migrate.

The preferences migration fails on after('avatar'). The published migration positions the column after avatar. If your users table has no such column, remove the ->after('avatar') call in your published copy.


Testing

composer install
composer test

The suite runs on Pest + Orchestra Testbench against an in-memory sqlite database.


License

MIT © DevDojo

About

The Notifications package for the DevDojo Platform

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages