Painless Laravel Broadcasting with SSE.

0.6.0 2023-03-07 18:23 UTC

This package is auto-updated.

Last update: 2023-03-23 13:50:12 UTC


Laravel Wave Logo

Bring live to your application

Build Status Styles check Types check Refactor code Total Downloads Latest Stable Version

Laravel Wave Demo


Laravel has brilliant broadcasting system for sending events from server to client. Imagine that real-time broadcasting is possible through native HTTP without any WebSockets setup.

🗼 Meet the Server-sent Events! Which works with default redis broadcasting driver and supports Laravel Echo. SSE is specially tuned to send events from the server to client through the HTTP protocol.

Demo with stream of tweets 🐤


I have spent a lot of effort playing with SSE, Laravel broadcasting system and Redis to prepare Laravel Wave and make it available for everyone. Since of February 24, unfortunately I haven't any commercial work, permanent living place or the ability to plan anything for the long term. However, I have a greater desire to continue creating useful solutions for people around the world. It makes me feel better these days.

support me

GitHub Sponsorships profile is ready! There you can find current work, future plans, goals and dreams... Your stars make me happier each day ✨ Sponsorship will enable us to live more peacefully and continue to work on useful solutions for you.

I would be very grateful for mentions or just a sincere "thank you".

💳 Sponsoring directly to savings jar with card or Apple Pay/Google Pay.


First release 🎉 Works well at home, but should be battle tested before 1.0. Feedbacks appreciated!

You can install packages to server and client sides via composer with npm:

composer require qruto/laravel-wave
npm install laravel-wave

Change broadcast driver in your .env file:



After installation, the server is ready to send broadcast events. Let's setup the client part.

📄 Broadcasting Documentation

With Laravel Echo

Import Laravel Echo with WaveConnector and pass it to the broadcaster option:

import Echo from 'laravel-echo';

import { WaveConnector } from 'laravel-wave';

window.Echo = new Echo({ broadcaster: WaveConnector });

In a fresh application, you can find Echo connection sources in resources/js/bootstrap.js file.

Replace it by the snippet above:

Show diff
- import Echo from 'laravel-echo';

- import Pusher from 'pusher-js';
- window.Pusher = Pusher;

- window.Echo = new Echo({
-     broadcaster: 'pusher',
-     key: import.meta.env.VITE_PUSHER_APP_KEY,
-     wsHost: import.meta.env.VITE_PUSHER_HOST ?? `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`,
-     wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80,
-     wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443,
-     forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https',
-     enabledTransports: ['ws', 'wss'],
- });
+ import Echo from 'laravel-echo';

+ import { WaveConnector } from 'laravel-wave';

+ window.Echo = new Echo({ broadcaster: WaveConnector });

Now you can use Echo as usual.

Receiving Broadcasts

Wave Models

With native conventions of Model Events Broadcasting and Broadcast Notifications you can use Wave models to receive predefined events.

import Wave from 'laravel-wave';

window.Wave = new Wave();

wave.model('User', '1')
    .notification('team.invite', (notification) => {
    .updated((user) => console.log('user updated', user))
    .deleted((user) => console.log('user deleted', user))
    .trashed((user) => console.log('user trashed', user))
    .restored((user) => console.log('user restored', user))
    .updated('Team', (team) => console.log('team updated', team));

Let's start by passing model name and key to the model method of the Wave instance.

By default Wave prefixes model name with App.Models namespace. You can override it with namespace option:

window.Wave = new Wave({ namespace: 'App.Path.Models' });

Persistent Connection / Fighting with Timeouts

Depend on web server configuration you may notice that the connection drops at a certain interval. Wave automatically reconnecting after request timeout. Don't worry to lost events during reconnection, Laravel Wave stores events history in one minute by default. You can change resume_lifetime value in the config file.

❇️ Interval between events should be less than web server request timeout and no other low-level timeout options set, to save connection persisted.

Wave try to send ping event during SSE connection request if the last event occurred earlier than the number of seconds set in ping.frequency config value. If application does not expect SSE connections frequently, specify the environment on which a ping event will be sent each Wave request. Default is local.

Manual Ping Control

If you want to control ping event by your own, disable automatic sending in the ping.enable config value.

Laravel Wave provides simple sse:ping command which can send a single ping or working with interval.

Tasks scheduler can help send ping event every minute:

protected function schedule(Schedule $schedule)

When you need shorter interval between ping events, run command with --interval option which receives number of seconds:

php artisan sse:ping --interval=30

For example, basic fastcgi_read_timeout value is 60s for Nginx + PHP FastCGI server setup. Which means that events in the connection must occur more often than 60 seconds to save it persistent.

Web Server

Looks like web servers weren't expect persisted HTTP connections and set traps at several stages 😟

Using Nginx + PHP FPM setup, usually connection limited to 1m by FastCGI.

Add next location directive after the end of location ~ \.php$ body:

location = /wave {
    rewrite ^/wave$ /index.php?$query_string break;
    fastcgi_split_path_info ^(.+\.php)(/.+)$;
    fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
    fastcgi_index index.php;
    include fastcgi_params;
    fastcgi_read_timeout 2m;

* copy fastcgi_pass unix socket path from location ~ \.php$.

Low Level PHP FPM Timeout

For example, Laravel Forge configures PHP FPM pool with request_terminate_timeout = 60 which forces to terminate all requests after 60 seconds.

You can disable it in /etc/php/8.1/fpm/pool.d/www.conf config file:

request_terminate_timeout = 0

or configure another pool for SSE connection:

Writing instruction...


You can publish the config file with:

php artisan vendor:publish --tag="wave-config"

This is the contents of the published config file:

return [

    | Resume Lifetime
    | Here you may specify the number of seconds that you wish an event stream
    | to be persisted to resume it after reconnect. The connection is
    | immediately re-established every closed response.
    'resume_lifetime' => 60,

    | Ping
    | Automatically sends a ping event during SSE connection request if the
    | last event occurred before the `frequency` value set in seconds.
    | It's necessary to keep the connection persisted.
    | By setting `eager_env` option a ping event will be sent each request.
    | It suits for development purposes or in case if the application
    | is not expecting events frequently. Accepts `array` or `null`.
    | For manual ping event control with `sse:ping` command
    | you can disable this option.
    'ping' => [
        'enable' => true,
        'frequency' => 30,
        'eager_env' => 'local', // null or array

    | Routes Path
    | This path will be used to register necessary routes for Wave connection,
    | presence channel users storing and simple whisper events.
    'path' => 'wave',

     | Route Middleware
     | Here you may specify which middleware Wave will assign to the routes
     | that it registers. When necessary, you may modify these middleware;
     | however, this default value is usually sufficient.
    'middleware' => [

     | Auth & Guard
     | Default authentication middleware and guard type for authenticate
     | users for presence channels and whisper events
    'auth_middleware' => 'auth',

    'guard' => 'web',


If you want to change base path from wave to another, don't forget to pass it in Echo or Wave instance:

window.Echo = new Echo({
   broadcaster: WaveConnector,
   endpoint: 'custom-path',

// or

window.Wave = new Wave({ endpoint: 'custom-path' });

Future Plans

  • 📍 local broadcasting driver
  • ◻️ Laravel Octane support
  • 📥 📤 two ways live models syncing
  • 📡 Something awesome with opened live abilities...


composer test


Please see CHANGELOG for more information on what has changed recently.


Please see CONTRIBUTING for details.

Security Vulnerabilities

Please review our security policy on how to report security vulnerabilities.


Package template based on Spatie Laravel Skeleton.


The MIT License (MIT). Please see License File for more information.