emuniq/filament-browser-notifications
Automatically turns every Filament database notification into a browser push notification, no code changes required. Installs VAPID keys, migrations, and the User trait in one command, then auto-registers across all panels with a subscription prompt and profile section.
Every sendToDatabase() in your app automatically triggers a browser push notification — no code changes needed.
Subscription prompt — appears after a configurable delay, asking the user to enable push notifications.

Profile section (inactive) — auto-injected on the Edit Profile page. One-click to enable.

Profile section (active) — shows subscription status, device count, and a button to disable.

composer require emuniq/filament-browser-notifications
php artisan browser-notifications:install
That's it. The install command handles everything:
.envpush_subscriptions migrationHasPushSubscriptions trait to your User model automaticallyNo manual plugin registration needed — it auto-registers on all Filament panels. No service worker publishing needed — it's served from a route automatically. No profile page changes needed — the subscription management section auto-injects.
Note: The trait step requires the process running
artisanto have write access to your User model (e.g.app/Models/User.php). In setups where the model is read-only for PHP (common in some Docker images), the command skips that step and prints instructions to add the trait by hand — it won't fail the install. To do it manually, add theHasPushSubscriptionstrait to your User model:use NotificationChannels\WebPush\HasPushSubscriptions; class User extends Authenticatable { use HasPushSubscriptions, Notifiable; }
Your existing code (unchanged)
|
| Notification::make()->sendToDatabase($users)
| $user->notify(new YourNotification) // with 'database' channel
|
'--> DatabaseNotification::created <-- plugin hooks here automatically
|
+-- Extracts title, body, action URL from notification data
+-- Throttles: groups burst notifications into one push
+-- Dispatches queued job (non-blocking)
|
'--> Web Push via VAPID --> Browser shows native OS notification
|
'--> Click: focuses app tab, navigates to action URL
Core
DatabaseNotification::created — no per-notification wiringThrottle & Grouping
throttle_seconds in config (set to 0 to disable)UI
Plugin
manifest.json, detects iOS Safari, guides users to Add to Home Screenqueue:work, etc.)Push notifications on iOS require the app to be added to the Home Screen as a PWA. The plugin handles this automatically:
manifest.json with display: standalone and icons from your panel faviconThe manifest's start_url and theme_color are derived from your active Filament panel — the panel path and primary color respectively — so the installed PWA opens the right route with on-brand chrome. Override either one in config/browser-notifications.php if needed:
'manifest' => [
'start_url' => '/backoffice', // null = derive from the panel path
'theme_color' => '#10B981', // null = derive from the panel primary color
],
After installation, verify push notifications work:
php artisan browser-notifications:test 1 # queue the test push
php artisan browser-notifications:test 1 --sync # send immediately
Works out of the box. To customize, override the auto-registered plugin in your PanelProvider:
use Emuniq\FilamentBrowserNotifications\BrowserNotificationsPlugin;
->plugins([
BrowserNotificationsPlugin::make()
->promptDelay(5) // seconds before showing prompt (default: 2)
->dismissCooldownDays(14) // days before re-prompting (default: 7)
->profileSection(false), // disable auto-injected profile section
])
Publish the config for queue and throttle settings:
php artisan vendor:publish --tag=browser-notifications-config
// config/browser-notifications.php
return [
'prompt_delay' => 2,
'queue_connection' => null,
'queue_name' => null,
'cleanup_dead_subscriptions' => true,
'throttle_seconds' => 5, // 0 to disable grouping
];
Add 'silent' => true via the notification's view data to skip the push (Filament's Notification has no ->data() method — use ->viewData()):
Notification::make()
->title('Low priority update')
->body('...')
->viewData(['silent' => true]) // no browser push for this one
->sendToDatabase($users);
Ships with English and Spanish:
php artisan vendor:publish --tag=browser-notifications-lang
.envGenerate VAPID keys manually:
php artisan tinker --execute="echo json_encode(\Minishlink\WebPush\VAPID::createVapidKeys());"
Add to .env:
VAPID_PUBLIC_KEY=<publicKey>
VAPID_PRIVATE_KEY=<privateKey>
VAPID_SUBJECT=https://your-app.com
sendToDatabase() / $user->notify()
| DatabaseNotification::created (Eloquent event)
v
SendWebPushOnDatabaseNotification (Listener)
| Checks: has subscriptions? silent? throttle window?
v
SendDatabaseNotificationWebPush (Queued Job, delayed if throttled)
| Counts recent unread notifications
| 1 notification: sends original title/body
| N notifications: sends grouped "You have N new notifications"
v
GenericWebPushNotification -> WebPushChannel -> VAPID -> Push endpoint
v
Service Worker (sw.js) -> showNotification() -> notificationclick -> navigate
MIT
This plugin is built and maintained by Emuniq — a Laravel & Filament consultancy based in Mexico. We help teams ship admin panels, custom Filament resources, and SaaS backoffices.
How can I help you explore Laravel packages today?