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

Browser Notifier Laravel Package

gupalo/browser-notifier

gupalo/browser-notifier is a Laravel package that simplifies sending browser notifications from your application, letting you alert users with native-style popups. It provides a straightforward API for triggering notifications and integrating them into your existing Laravel workflows.

View on GitHub
Deep Wiki
Context7

Getting Started

gupalo/browser-notifier is a thin Laravel bridge over Symfony Notifier's Browser transport, which pushes messages to open browser tabs through a Mercure hub. You are not sending Web Push / VAPID notifications to closed tabs — a live EventSource connection is required. Set expectations accordingly.

Minimal setup:

composer require gupalo/browser-notifier
php artisan vendor:publish --tag=browser-notifier-config   # if the provider ships a config

You need a Mercure hub. Locally, the Docker image is the fastest path:

docker run -d --name mercure -p 3000:80 \
  -e MERCURE_PUBLISHER_JWT_KEY='!ChangeMe!' \
  -e MERCURE_SUBSCRIBER_JWT_KEY='!ChangeMe!' \
  dunglas/mercure

Then wire the env vars the package expects (names vary by version — confirm against config/browser-notifier.php):

MERCURE_URL=http://localhost:3000/.well-known/mercure
MERCURE_PUBLIC_URL=http://localhost:3000/.well-known/mercure
MERCURE_JWT=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...   # publisher JWT

First use case — tell a user in an already-open tab that a queued export finished:

use Illuminate\Notifications\Notification;

class ExportReady extends Notification
{
    public function via($notifiable): array
    {
        return ['browser'];
    }

    public function toBrowser($notifiable)
    {
        return \Gupalo\BrowserNotifier\BrowserMessage::create('Export ready')
            ->topic("users/{$notifiable->id}/exports");
    }
}

$user->notify(new ExportReady($export));

Frontend side, subscribe to the topic and hand the payload to the Notification API:

const es = new EventSource(
  `${mercureUrl}?topic=${encodeURIComponent(`users/${userId}/exports`)}`,
  { withCredentials: true }
);
es.onmessage = (e) => new Notification('Export ready', JSON.parse(e.data));

Where to look first in the repo: the service provider (how the Symfony Notifier / BrowserTransport is bound and how config maps to it), the channel class registered with Laravel's Notification::extend or a via() alias, and the BrowserMessage-style DTO if one exists. If the package is a straight passthrough, Symfony's BrowserTransport / BrowserOptions docs are your real reference.

Implementation Patterns

Custom notification channel. The idiomatic integration is a Laravel channel aliased to 'browser' (or the fully-qualified class). Once registered you get the normal notification ergonomics — queueing, via() switching, per-user settings — for free:

public function via($notifiable): array
{
    return $notifiable->wants_browser_alerts ? ['browser', 'mail'] : ['mail'];
}

Topic strategy is the whole design. Mercure delivers by topic, so pick a scheme early and treat it as an API:

  • users/{id}/notifications — per-user, private (requires a subscriber JWT or signed cookie)
  • orders/{id} — per-entity, good for collaborative UIs
  • broadcast — public announcements, no auth

Prefer per-user private topics over a global topic filtered in JS; filtering in the browser leaks other users' payloads.

Queue it. Almost every real send happens after a slow operation (export, webhook, payment). Implement ShouldQueue on the notification or dispatch from a job listener. Symfony's transport is a synchronous HTTP call to the hub, so an unresponsive hub will stall the request otherwise.

Compose with other channels. Browser notifications should be additive, not replacing email/database. A common pattern: via() returns ['database', 'browser'] so the in-app bell and the live toast stay consistent, both built from the same notification class.

Direct service use. For non-user events (system alerts, ops dashboards), bypass the notification system and resolve the transport/service from the container. This is worth having a small wrapper for, so the rest of your app never imports Symfony\Component\Notifier types.

Testing. Laravel's Notification::fake() records the notification object regardless of channel, so you can assert assertSentTo without hitting Mercure. For end-to-end confidence, bind a fake transport in the container in your test case and assert on the published topic/payload. Do not let CI talk to a real hub.

Gotchas and Tips

  • Mercure is a hard dependency, not a suggestion. Without a reachable hub the transport throws at dispatch time. Guard dispatch behind try/catch or a queue with retries; a dead hub should never fail a business transaction.
  • JWT confusion is the #1 onboarding trap. The publisher JWT (server → hub) and subscriber JWT (browser → hub) are signed with different keys in the standard setup, and Symfony's jwt option only covers the publisher side. If nothing arrives, check the browser network tab for 401 on the /.well-known/mercure request before touching PHP.
  • Notification API requires a secure context. localhost is exempt; any non-HTTPS staging host silently no-ops. Permission must also be requested from a user gesture — requesting on page load gets you a permanently denied prompt in Chrome.
  • Open tab required. Users who closed the tab receive nothing. If your product needs delivery to closed browsers, you want a Web Push / VAPID package instead; this package is not that.
  • Payload serialization. Mercure messages are strings; the transport typically JSON-encodes your data. If you pass Eloquent models or objects with no JsonSerializable, expect either a throw or a nested data blob on the client. Send scalars and primitive arrays; hydrate on the client if needed.
  • Queued notifications serialize the whole notification. Models attached to the notification must survive SerializesModels; avoid closures and resources. Classic Serialization of 'Closure' is not allowed if you slip one in.
  • Topic collisions across environments. A shared hub between staging and production will cross-deliver. Namespace topics ({env}/users/{id}/...) or run separate hubs per environment.
  • Notification::fake() does not assert channel-internal behaviour. It swallows the channel; it won't tell you the payload was malformed. Add a thin integration test against a fake transport if the hub contract matters to you.
  • Maturity risk is real. With zero dependents and a single maintainer, pin the version in composer.json and keep the integration surface small — ideally one class in your app that calls this package, so replacing it with a hand-rolled BrowserTransport wrapper (Symfony Notifier is already a first-class Composer package) is a one-file change.
  • Extension point: Symfony's BrowserOptions (topic, level, etc.) is where most customisation lives. If the package doesn't expose an option you need, subclass the channel rather than forking; the transport configuration is just DI.
  • Reconnection: EventSource reconnects automatically but drops messages sent while disconnected. For anything you can't afford to miss, pair the live toast with a durable database notification and a poll-on-reconnect.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings