Listen for Laravel events in Vue with Reverb

Install Reverb, broadcast a public event through the queue, and subscribe from a Vue page with useEchoPublic.

For your AI agent

Copies a prompt. Paste it into the AI agent you already have open.

Read https://laravel-vuejs.space/laravel-reverb-vue.md
Install Reverb, broadcast a public event through the queue, and subscribe from a Vue page with useEchoPublic.
Do not point that authorizer at a bearer token you stored for your own SPA.
Show the diff. Do not commit.

Next: GitHub login with Laravel Socialite and Vue

On this page · 9 sections
  1. Install
  2. The event
  3. The Vue page
  4. Private channels and a separate SPA
  5. Production
  6. Recap
  7. FAQ
  8. Next steps
  9. Sources

A new post lands and everyone else’s list stays stale until they refresh. Reverb pushes it to every open page. You need three moving parts running (queue, Reverb, Vite) and one composable; here they are.

TL;DR

  • php artisan install:broadcasting --reverb, then restart Vite.
  • Run a queue worker and php artisan reverb:start; broadcasts are queued.
  • Implement ShouldBroadcast, return a Channel, dispatch after the row exists.
  • Listen with useEchoPublic('posts', 'PostPublished', ...); useEcho is for private channels.

What you'll learn

  • How to install Reverb and Echo
  • How to broadcast an event
  • How to listen with useEchoPublic and debug silence

Who it's for: Laravel + Vue developers adding real-time updates

Applies to: Laravel 13 · Reverb · @laravel/echo-vue · Vue 3

Polling the posts list every few seconds works until it does not. Reverb is Laravel’s WebSocket server. Echo is the client. A Vue page can subscribe with useEchoPublic from @laravel/echo-vue and update when a PHP event fires.

The official write-up is the broadcasting docs. This page is the short path for a Vue starter kit.

  1. Install broadcasting with Reverb.
  2. Run the queue worker and Reverb.
  3. Broadcast an event.
  4. Listen on the Vue page.

Install#

bash
php artisan install:broadcasting --reverb

That installs Reverb, the Echo packages, and the VITE_REVERB_* keys. Restart Vite after it changes .env, or the browser will still see empty keys.

Broadcasts are queued. The setup guide starts a queue worker with composer run dev. If you are not using that script, run php artisan queue:work yourself. Then start the socket server:

bash
php artisan reverb:start

Gotcha: two processes, two silent failures

Leave both running. An event with no worker sits in the jobs table. An event with no Reverb process never reaches the browser.

The event#

bash
php artisan make:event PostPublished
app/Events/PostPublished.phpphp
namespace App\Events;

use App\Models\Post;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PostPublished implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    public function __construct(public Post $post) {}

    public function broadcastOn(): Channel
    {
        return new Channel('posts');
    }
}

ShouldBroadcast is the line that sends it. A public Channel needs no auth. Use PrivateChannel when only a logged-in user should hear it, and authorize that name in routes/channels.php.

Dispatch it after the row exists:

php
PostPublished::dispatch($post);

Heads up: public properties are public

Put that in store after Post::create(). The queued job serializes the model. Keep the payload small. Echo will hand the public properties to the browser, so hide anything you would not print on the page.

The Vue page#

resources/js/pages/posts/Index.vuevue
<script setup lang="ts">
import { ref } from "vue";
import { useEchoPublic } from "@laravel/echo-vue";

const titles = ref<string[]>([]);

useEchoPublic(
  "posts",
  "PostPublished",
  (event: { post: { title: string } }) => {
    titles.value.unshift(event.post.title);
  },
);
</script>

<template>
  <ul>
    <li v-for="title in titles" :key="title">{{ title }}</li>
  </ul>
</template>

Listens on private-posts, misses the event, then Matches the public Channel:

resources/js/pages/posts/Index.vuetypescript
useEcho("posts", "PostPublished", onPublished);
resources/js/pages/posts/Index.vuetypescript
useEchoPublic("posts", "PostPublished", onPublished);

useEchoPublic subscribes to the public channel when the component is set up and leaves it when the component goes away. useEcho is the private-channel hook, so it would listen on private-posts and miss this event. The event name matches the class name, PostPublished, unless you set broadcastAs().

Two windows

Create a post from another window. The title should appear without a reload. If it does not, check the queue worker, then Reverb, then the browser console for a failed WebSocket. A missing VITE_REVERB_APP_KEY fails quietly until you look.

Private channels and a separate SPA#

An Inertia app on the same origin can use the defaults install:broadcasting wrote. A Vue SPA on another port is the Sanctum case. Private channels then authorize through /api/broadcasting/auth, and Echo needs a custom authorizer that uses the credentialed Axios instance. The setup is in the Sanctum broadcasting section and depends on the cookie login from the Sanctum guide.

Gotcha

Do not point that authorizer at a bearer token you stored for your own SPA. Fix the cookie first.

Production#

Reverb is a process, like the queue worker. Laravel Cloud can run it as a managed WebSocket cluster and injects the REVERB_* variables. On a VPS you run php artisan reverb:start under a supervisor and proxy the socket port. The Vue bundle only needs the VITE_REVERB_* values from the production env at build time. The deploy guide covers the asset build. It does not start Reverb for you.

Recap#

  • Reverb is the server, Echo the client, the queue the courier.
  • Restart Vite after .env changes.
  • Public channel ↔ useEchoPublic; private channel ↔ useEcho + channels.php.
  • In production, Reverb is another supervised process.

FAQ#

Why does my Laravel Reverb event never reach Vue?

Check, in order: a queue worker is running, reverb:start is running, and the browser has a VITE_REVERB_APP_KEY (restart Vite after .env changes).

What is the difference between useEcho and useEchoPublic?

useEchoPublic listens on a public channel. useEcho listens on a private channel, so it subscribes to private-… and misses public events.

Do broadcasts need a queue worker?

Yes. ShouldBroadcast events are queued; without a worker they wait in the jobs table.

Next steps#

Sources#

Found this useful?

Ask questions, share what you built.

Join thousands of Laravel and Vue developers in our Facebook group, or get new posts in your Telegram feed. Sharing this article helps too.

Keep reading

Newsletter

Get the best of Laravel & Vue, once a month.

One short email with new guides, release notes worth reading and hand-picked community links. No spam, unsubscribe any time.