Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Filament Browser Notifications Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

Before you install, confirm the environment can actually deliver a push. The package has four hard requirements that are easy to overlook in a fresh project: HTTPS (Web Push API refuses to work on plain HTTP outside localhost), a running queue worker, PHP 8.1+ / Laravel 10+ / Filament 3+, and a User model that is writable by the process running artisan.

composer require emuniq/filament-browser-notifications
php artisan browser-notifications:install

That single command does four things you should verify immediately after it finishes:

  1. .env — appends VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT. Confirm VAPID_SUBJECT is a real mailto: or https:// URL; push services use it to contact you about abuse.
  2. Migrations — publishes and runs the push_subscriptions table (from laravel-notification-channels/webpush).
  3. app/Models/User.php — imports and applies HasPushSubscriptions. Open the file and confirm the trait is in the class body, not just the use statements (this was an actual bug fixed in 1.1.1).
  4. Panel registration — nothing to add; the plugin self-registers on every Filament panel.

Where to look first, in order:

  • php artisan vendor:publish --tag=browser-notifications-config → config/browser-notifications.php. Read this before you touch any notification code; it governs queue routing and the throttle window.
  • /admin/profile in the browser, logged in as yourself. The auto-injected section is the fastest way to confirm the whole stack (service worker route, VAPID keys, subscription persistence) is wired.
  • Your existing notification classes — they should need zero edits.

First use case: take a notification you already send, e.g.

Notification::make()
    ->title('Invoice paid')
    ->body('Invoice #1042 was paid by Acme Ltd.')
    ->sendToDatabase($users);

Enable push in your own profile, then fire the test command to isolate package problems from your own code:

php artisan browser-notifications:test 1 --sync   # immediate, bypasses queue
php artisan browser-notifications:test 1         # queued, proves worker is alive

If --sync works but the plain command doesn't, your queue worker is the problem, not the package.

Implementation Patterns

The default pattern is "change nothing." The listener hooks DatabaseNotification::created globally, so every existing sendToDatabase() call — and every $user->notify(new SomeNotification) that writes a database channel row — starts producing pushes. The most common integration task in a mature codebase isn't adding code, it's auditing which existing notifications now hit users' operating systems.

Selective suppression. Not every database notification deserves an OS-level interruption. Mark the low-value ones:

Notification::make()
    ->title('Weekly digest ready')
    ->viewData(['silent' => true])   // note: viewData, not data()
    ->sendToDatabase($users);

A sensible convention in a real app: silently write to the bell icon for anything the user can catch up on, and let only genuinely time-sensitive events (approval requests, payment failures, assignment) reach the browser.

Tuning the throttle for your domain. The 5-second default groups bursts into a single "You have N new notifications" push. That is right for fan-out events (a bulk import finishing, a mass assignment), wrong for a transactional app where each notification carries distinct meaning. Drop it to 0 to disable grouping if individual titles matter more than volume — but then audit any loop that notifies the same user repeatedly, because you'll get one OS popup per iteration.

Queue isolation. Route push dispatch to its own queue so a slow push endpoint never delays your mail or exports:

'queue_connection' => 'redis',
'queue_name' => 'notifications',

Panel provider override. The plugin auto-registers, but adding it explicitly in PanelProvider::panel()->plugins([...]) overrides the auto-registration — that's the supported way to change behaviour, and it's how you scope the feature per panel (e.g. prompt after 2s in the admin panel, disabled entirely on a customer-facing portal by simply not adding it there).

BrowserNotificationsPlugin::make()
    ->promptDelay(5)
    ->dismissCooldownDays(14)
    ->profileSection(false),   // when you build your own preferences UI

Bring-your-own UI. profileSection(false) plus the HasPushSubscriptions trait gives you $user->pushSubscriptions to count devices yourself, if you want push preferences to live inside a broader "Notification settings" resource rather than the profile page.

iOS is a different onboarding flow, not a bug. Treat "add to home screen" as a required step in your user docs for iOS users; the package detects Safari-without-PWA and shows instructions in the profile section, but it cannot bypass the platform requirement.

Gotchas and Tips

  • It hooks the DatabaseNotification model, not Filament. Any notification that persists to the notifications table triggers a push — including ones written by packages or by plain Laravel Notification classes with custom toDatabase() payloads. Test your non-Filament notifications separately; title/body/URL extraction is tuned for Filament's data shape and custom payloads may yield a generic push.
  • Non-User notifiables aren't covered. The installer only patches User. If you notify Team, Admin, or a multi-guard model, add HasPushSubscriptions there by hand and expect the profile section not to appear for them.
  • Read-only filesystem in Docker. The installer degrades gracefully and prints the manual trait snippet instead of failing — but it still reports success. If profile push controls are missing after an install, check the model first.
  • Trait ordering matters with other traits. use HasPushSubscriptions, Notifiable; — the 1.1.1 fix handles use Notifiable, HasRoles; style declarations, but if you're on an older tag and hit BadMethodCallException on pushSubscriptions(), that's the cause.
  • HTTPS failures look like silence. If the subscription never registers, check the service worker route loads (/sw.js or similar) and that the page is served over HTTPS. Browser devtools → Application → Service Workers is where you'll actually see the failure; server logs won't show anything.
  • The dismiss cooldown is client-side. dismissCooldownDays is enforced via localStorage, so it resets when a user clears storage or switches device. Don't treat it as a security or compliance control.
  • Permission-denied is a dead end for the plugin. Once a user hits "Block" in the browser, no server-side action re-opens the prompt; they must reset it in site settings. The profile section offers a reload button, but your support docs should say the same thing.
  • Action URLs and ActionGroup. Nested actions were only resolved as of 1.2.0, and pre-1.2 grouped pushes fell back to a hardcoded /admin. If you run multiple panels or a non-/admin path, be on ≥1.2.0 or clicks will land on the wrong page.
  • manifest.json inheritance. start_url and theme_color derive from the active panel, which is usually right — but theme_color conversion is per-Filament-major (RGB triplets in v3, oklch() in v4/v5). Verify the installed PWA chrome after a Filament upgrade; pin overrides in config if you need stability.
  • Debugging order that saves time: --sync test → queue worker → push_subscriptions row exists for the user → service worker registered → VAPID keys match the environment. Most "it doesn't work" reports resolve at step 3 or 5, especially after copying a database between environments without copying .env.
  • Maturity caveat. 15 stars, no dependents, single maintainer, rapid 1.0 → 1.2 iteration with real behavioural fixes (hardcoded /admin, trait patching, silent in v5). Pin a version, read the changelog before upgrading, and keep the surface small: you're adopting a listener on a core Eloquent event, so a regression has app-wide blast radius. The escape hatch is good — it's an event listener and a queued job, both replaceable without touching your notification classes.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums