suenerds / laravel-mercure-broadcaster
Mercure broadcaster
Package info
github.com/suenerds/laravel-mercure-broadcaster
pkg:composer/suenerds/laravel-mercure-broadcaster
Requires
- php: ^8.3
- illuminate/broadcasting: ~10.0|~11.0|^12.0|^13.0
- lcobucci/jwt: ^5.0
- symfony/mercure: ^0.8
Requires (Dev)
- laravel/pint: ^1.31
- orchestra/testbench: ~8.0|~9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.5|^12.5
- symfony/process: ~6.0|~7.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-08 09:15:09 UTC
README
Laravel broadcaster for Mercure for doing Server Sent Events in a breeze.
- Works with Laravel's native
ChannelandPrivateChannelclasses - Publish one update to multiple topics at once with the package's channel classes
- JSON payloads for classic events, raw SSE data lines for Datastar-style events (e.g. suenerds/laravel-datastar)
- Built-in channel authorization endpoint that redirects the browser's
EventSourceto the hub with a Mercure JWT cookie
Installation
Requires PHP 8.3+ and Laravel 10 or newer.
Install the package via Composer:
composer require suenerds/laravel-mercure-broadcaster
Configure Laravel to use the Mercure broadcaster by editing config/broadcasting.php:
<?php return [ 'default' => env('BROADCAST_DRIVER', 'mercure'), 'connections' => [ // ... 'mercure' => [ 'driver' => 'mercure', // URL your Laravel app uses to publish updates to the hub 'url' => env('MERCURE_URL', 'http://localhost:3000/.well-known/mercure'), // URL browsers use to subscribe; defaults to `url` when omitted 'public_url' => env('MERCURE_PUBLIC_URL'), 'secret' => env('MERCURE_SECRET', 'aVerySecretKey'), ], ], ];
Usage
Broadcasting events
Add an event which implements the ShouldBroadcast interface like in the
Laravel broadcasting docs.
Mercure topics are URIs, so use a topic URI as the channel name:
<?php namespace App\Events; use Illuminate\Broadcasting\Channel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; class NewsItemCreated implements ShouldBroadcast { public function __construct(public NewsItem $newsItem) { } public function broadcastOn() { return new Channel('http://example/news-items'); } public function broadcastAs(): string { return 'news-item.created'; } }
Each update is published with an SSE event type: the broadcastAs() name, or the event's
class name if you don't define one. Listen for that type in your frontend — a plain
message listener will not fire for typed events:
const url = 'http://localhost:3000/.well-known/mercure?topic=' + encodeURIComponent('http://example/news-items'); const es = new EventSource(url); es.addEventListener('news-item.created', (messageEvent) => { const eventData = JSON.parse(messageEvent.data); console.log(eventData); });
Publishing to multiple topics
Laravel's channel classes carry a single topic. To publish one update to several topics, use this package's channel classes instead:
use Suenerds\LaravelMercureBroadcaster\Broadcasting\Channel; public function broadcastOn() { return new Channel([ 'http://example/news-items', "http://example/authors/{$this->newsItem->author_id}/news-items", ]); }
Suenerds\LaravelMercureBroadcaster\Broadcasting\PrivateChannel does the same for private
updates.
Payload formats
The broadcast payload (your event's public properties, or whatever broadcastWith()
returns) is serialized in one of two ways:
- An associative array is JSON-encoded — decode it with
JSON.parseas shown above. - A list of strings is joined verbatim, each entry becoming its own
data:line of the SSE event. This is what Datastar expects, so events using the traits from suenerds/laravel-datastar broadcast correctly out of the box:
<?php namespace App\Events; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Contracts\Broadcasting\ShouldBroadcast; use Illuminate\View\View; use Suenerds\LaravelDatastar\IsPatchElementsEvent; class OrderShipped implements ShouldBroadcast { use IsPatchElementsEvent; public function __construct(public Order $order) { } public function broadcastOn() { return new PrivateChannel("http://example/user/{$this->order->user_id}/orders"); } public function elements(): View { return view('orders.card', ['order' => $this->order]); } }
Private channels
Private channels are baked into Mercure and secured with a JWT cookie. Events broadcast
on a PrivateChannel (Laravel's or this package's) are published as private updates, and
the hub only delivers them to subscribers whose JWT grants the topic.
First, authorize your channels in routes/channels.php — exactly like any other Laravel
broadcaster. A channel is denied unless a matching callback returns a truthy value.
Placeholders such as {id} match a single segment; they stop at . and /:
use Illuminate\Support\Facades\Broadcast; Broadcast::channel('http://example/user/{id}/direct-messages', function ($user, $id) { return (int) $id === $user->id; });
The endpoint accepts the requested channels as a channels[] array or a comma-separated
channels query string. It authorizes each channel, then responds with a redirect to the
hub's public URL (with a topic parameter per channel) and sets a mercureAuthorization
cookie granting exactly those topics. An EventSource follows that redirect, so your
frontend can subscribe straight through the endpoint:
const url = '/broadcasting/auth?channels=' + encodeURIComponent('http://example/user/1/direct-messages'); const es = new EventSource(url, { withCredentials: true }); es.addEventListener('direct-message.created', (messageEvent) => { const eventData = JSON.parse(messageEvent.data); console.log(eventData); });
Because Laravel encrypts cookies by default, add an
exception for the
mercureAuthorization cookie in your cookie encryption configuration.
->withMiddleware(function (Middleware $middleware): void { $middleware->encryptCookies(except: ['mercureAuthorization']); })
The cookie can only be set when the hub's public URL shares a second-level domain with
your app (e.g. app.example.com and mercure.example.com). With the hub on an
unrelated domain, the underlying Symfony component refuses to create the cookie.
Advanced usage
The service provider registers its collaborators with singletonIf, so any binding you
define in your application wins over the package default:
Symfony\Component\Mercure\HubInterface— the hub itself (also aliased toSymfony\Component\Mercure\Hubandmercure.hub)Symfony\Component\Mercure\Jwt\TokenProviderInterface— provides the JWT used to publish updatesSymfony\Component\Mercure\Jwt\TokenFactoryInterface— creates the subscriber JWTs for authorization cookies
If you only want to customize the publisher JWT (custom claims, another signing
algorithm, …), override the suenerds.mercure_broadcaster.publisher_jwt service; it must
resolve to a JWT string. See
LaravelMercureBroadcasterServiceProvider
for how the default is generated.
$this->app->singleton('suenerds.mercure_broadcaster.publisher_jwt', function () { return MyJwtBuilder::publisherToken(); });
Further reading
Make sure you read the documentation of Mercure and how to run it securely (behind https).
Testing
composer test # full suite; needs a running Docker daemon vendor/bin/phpunit --exclude-group docker # fast suite, no Docker required composer lint # code style
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Credits
This package started as a fork of mvanduijker/laravel-mercure-broadcaster.
License
The MIT License (MIT). Please see License File for more information.