Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

linzeclark's avatar

Bell notification to pusher how to show them update in real-time with echo? Help!

So I’m learning Laravel and so far doing ok in general but I’ve yet to really dive into Vue components.

I’m trying to setup real-time notifications in my admin panel using events and pusher api then using echo to update the notifications.

Ive got the database notifications all setup and when a new User fills a form in to enquire about signing up, a controller creates a database notification which updates the notification database and broadcasts the enquiry to pusher api. I have the notifications displayed in a similar way to Facebook in a bell icon, which lists the most recent notifications for all admins users to view, but atm requires the page to refresh to clear any read notifications and view new ones.

What I’m stuck on his how to real-time update the notification count and drop-down notifications using echo.

I can see that pusher is working and getting the notification event but I’m confused as to how to implement the vue component as the notification drop down/bell.

Any hints on where to start on this please. I’m currently developing on local host.

0 likes
17 replies
LaryAI's avatar
Level 58

To implement real-time updates for your notification bell using Laravel Echo and Pusher, you'll need to follow these steps:

  1. Install and Configure Laravel Echo and Pusher: First, make sure you have Laravel Echo and Pusher installed and configured in your Laravel application.

    npm install --save laravel-echo pusher-js
    

    In your resources/js/bootstrap.js file, uncomment or add the following configuration:

    import Echo from 'laravel-echo';
    
    window.Pusher = require('pusher-js');
    
    window.Echo = new Echo({
        broadcaster: 'pusher',
        key: process.env.MIX_PUSHER_APP_KEY,
        cluster: process.env.MIX_PUSHER_APP_CLUSTER,
        encrypted: true
    });
    

    Make sure to set the appropriate Pusher keys and cluster in your .env file.

  2. Broadcast Events: Ensure that your event class implements ShouldBroadcast and is set up correctly to broadcast on a channel.

    use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
    
    class NewUserEnquiryEvent implements ShouldBroadcast
    {
        // Event content here
    
        public function broadcastOn()
        {
            return new Channel('notifications');
        }
    }
    
  3. Listen for Events in Vue Component: In your Vue component, you'll need to listen for the event using Laravel Echo. Here's an example of how you might do this:

    <script>
    export default {
        data() {
            return {
                notifications: [],
                unreadCount: 0
            };
        },
        mounted() {
            this.listenForNotifications();
        },
        methods: {
            listenForNotifications() {
                Echo.channel('notifications')
                    .listen('NewUserEnquiryEvent', (event) => {
                        this.notifications.push(event.notification);
                        this.unreadCount++;
                    });
            },
            markAsRead(notificationId) {
                // Logic to mark notification as read
                // Update unreadCount accordingly
            }
        }
    }
    </script>
    
  4. Update Your Blade Template: In your Blade template, you'll need to include the Vue component and pass any initial data it might need.

    <notifications-component :initial-notifications="{{ $notifications }}" :initial-unread-count="{{ $unreadCount }}"></notifications-component>
    
  5. Handle Authentication (Optional): If you need to restrict the channel to authenticated users, you can use private channels and modify your broadcastOn method to return a PrivateChannel instance.

    public function broadcastOn()
    {
        return new PrivateChannel('notifications.' . $this->user->id);
    }
    

    And in your channels.php file, define the authorization logic for the private channel:

    Broadcast::channel('notifications.{userId}', function ($user, $userId) {
        return (int) $user->id === (int) $userId;
    });
    

    Then, in your Vue component, subscribe to the private channel:

    Echo.private('notifications.' + userId)
        .listen(...)
    

Remember to compile your JavaScript assets with npm run dev or npm run production after making changes to your JavaScript files.

This should give you a real-time notification system where the notification count and dropdown are updated without needing to refresh the page.

gych's avatar

Ok so from what I understand from your post is that you've already successfully set up everything on the back end side and that pusher is receiving the events.

Now you want to implement this on the front end with vue, did you already attempt to import laravel-echo and pusher on the client side?

linzeclark's avatar

@gych hi thanks, yeah pusher, echo is installed, I think it’s mainly I’m stuck on how to actually code the Vue component. Ive not really learnt much of that yet.

I’ve followed a few tutorials but to no avail, I’m developing locally using sail/docker atm.

I can see the event in my web console. It’s literally this last bit that’s probably pretty simple but for some reason my brain is not getting it. Maybe I need more coffee 😬

gych's avatar

@linzeclark Ok I'll do my best to guide you.

Did you already try to add a listener in you vue component? This is an example, its for a private channel

window.Echo.private("YOUR CHANNEL").listen("YOUR EVENT", (e) => {

});
linzeclark's avatar

@gych yes, sorry I’d post some code but I’m out at my kids karate class!

I have event ‘NewLeadEvent’

It broadcasts a notification ‘name’ = ‘persons name’ ‘Enquiry’ = ‘their enquiry’

These currently send out to the notification database, and a slack api message. In the pusher event debug I see the above. So that’s all working pretty ok.

Ive setup the event listener for the channel ‘admin-notifications’ which is currently public. In my app.js

It’s a typical bootstrap 5 drop-down menu that lists all the notifications on my blade view. With a notification count.

Thank you so much. I will post my code later.

linzeclark's avatar

ok so my bootstrap.js file:

 import axios from 'axios';
window.axios = axios;

window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';

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,
     cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'eu',
     wsHost: import.meta.env.VITE_PUSHER_HOST ? 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'],
 });

app.js file as follows

window.Echo.channel('admin_notifications')
    .listen('NewLeadEvent', e => {

    })
var notifications = [];
const NOTIFICATION_TYPES = {
    follow: 'App\Notifications\NewLead'
};

I know there most be more to the above but ive tried a few tutorials and I'm still head scratching right now.

My actual blade view drop down as it stands, which just gets the standard laraval notification database table data for the current admin user.

        Route::get('/markAsRead', function () {Auth()->user()->unreadNotifications->markAsRead();});
 <li class="nav-item dropdown dropdown-large">

                        @php
                            $unreadNotifications = auth()->user()->unreadNotifications;
                         @endphp
                        <a class="nav-link dropdown-toggle dropdown-toggle-nocaret position-relative" href="#" onclick="markNotificationsAsRead()" data-bs-toggle="dropdown">
                            @if($unreadNotifications->count() >0)
                            <span class="alert-count">
                              {{ $unreadNotifications->count() }}
                            </span>
                            @endif
                            <i class='bx bx-bell'></i>
                        </a>



                        <div class="dropdown-menu dropdown-menu-end">
                            <a href="javascript:;">
                                <div class="msg-header">
                                    <p class="msg-header-title">Notifications</p>
                                    <p class="msg-header-badge">{{ $unreadNotifications->count() }} New</p>
                                </div>
                            </a>
                            <div class="header-notifications-list">

                                @foreach ($unreadNotifications as $notification)




                                <a class="dropdown-item" href="javascript:;">
                                    <div class="d-flex align-items-center">
                                       {{-- <div class="user-online">
                                        </div>--}}
                                       @if($notification->type === 'App\Notifications\NewLead')
                                            <img src="{{asset('backend/assets/images/icons/icon-newlead.png')}}" class="msg-avatar" alt="user avatar">
                                       @endif
                                        <div class="flex-grow-1">
                                            <h6 class="msg-name">{{ $notification->data['name'] }}<span class="msg-time float-end">{{ $notification->created_at->diffForHumans() }}
												ago</span></h6>
                                            <p class="msg-info"> {{ $notification->data['message'] }}</p>
                                            <p class="msg-info"> {{ $notification->data['enquiry'] }}</p>
                                        </div>
                                    </div>
                                </a>
                                @endforeach
                            </div>
                            <a href="javascript:;">
                                <div class="text-center msg-footer">
                                    <button class="btn btn-primary w-100">View All Notifications</button>
                                </div>
                            </a>
                        </div>
                    </li>

Then i've played around with creating an event and also using the existing notification class to send to pusher.

this is the event class

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

    public $newLead;

    public function __construct($newLead)
    {
        $this->newLead = $newLead;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel|array
     */
    public function broadcastOn()
    {
        return new Channel('admin_notifications');
    }

    public function broadcastWith()
    {
        return [

                'name' => $this->newLead->name . ' ' . $this->newLead->last_name,
                'enquiry' => $this->newLead->enquiry,

        ];
    }
}

using the below in my controller

$newLead->save();
        /** Create a new Event */
        event(new NewLeadEvent($newLead));

or using this notification class instead

class NewLead extends Notification
{
    use Queueable;

    public $user;
    public function __construct($newUser)
    {
        //
        $this->user = $newUser;
    }

    /**
     * Get the notification's delivery channels.
     *
     * @return array<int, string>
     */
    public function via(object $notifiable): array
    {
        return ['database', 'broadcast'];
    }


    /**
     * Get the array representation of the notification.
     *
     * @return array<string, mixed>
     */
    public function toArray(object $notifiable): array
    {
        return [
            //
            'name' =>  $this->user->name. " ". $this->user->last_name,
            'enquiry' =>  $this->user->enquiry,
            'message' => 'New Website Enquiry'
        ];
    }




    public function toBroadcast($notifiable): BroadcastMessage
    {
        return new BroadcastMessage([
            'name' =>  $this->user->name. " ". $this->user->last_name,
            'enquiry' =>  $this->user->enquiry,
            'message' => 'New Website Enquiry'
        ]);
    }
}

which send via my controller only to Admin users using scope on the user model.

 $adminUsers = User::admin()->get();
        /** Send Administration notification */
        Notification::sendNow($adminUsers, new NewLead($newLead));

So both ways work but I only really want admin to see these notifications. I know that the channel should be private too but one step at a time.

I was hoping to have all notifications show up for admin for all kinds of activity in the app from new subscriptions, new users or any kind of message sent from a user.

Once I've kind of got the client side bit sorted in my mind I think I'll be ok but it's just a mental block with me. ha ha. Any pointers would be greatly appreciated to get me going. thanks you!

gych's avatar

Ok so first thing you should do now while you're working on this, is to enable pusher to log to your browser dev tools console.

You can do that by adding this in the boostrap js file under window.Pusher

Pusher.logToConsole = true;

After that add a console log to the listener in your app.js file for now

window.Echo.channel('admin_notifications')
    .listen('NewLeadEvent', e => {
		console.log(e);
    })

If these logs show the right information, you know that it works and can proceed to implement it in the template. In your op you said the you want to use vue but I see that you're using blade template?

linzeclark's avatar

@gych thanks i'm using blade templates, its setup using breeze, I set up the database notifications first but they require a page refresh to update the data so I figured my next step is to try to make them real-time update using pusher.

My console log after just doing a form enquiry submission:

Pusher :  : ["Event recd",{"event":"pusher_internal:subscription_succeeded","channel":"admin_notifications","data":{}}] logger.ts:19:21
Pusher :  : ["No callbacks on admin_notifications for pusher:subscription_succeeded"] logger.ts:19:21
Pusher :  : ["Event recd",{"event":"App\Events\NewLeadEvent","channel":"admin_notifications","data":{"name":"test test`","enquiry":"Senior Dragons - Elite"}}] logger.ts:19:21
Object { name: "test test`", enquiry: "Senior Dragons - Elite" }
app.js:16:16

So its seeing the data.

gych's avatar

@linzeclark Oke that's good than now the only thing you got to do is to implement this in your blade template.

You can use vue and integrate the vue component in your existing blade template. The component will display all the notifications.

Or just use javascript and manipulate the dom with the new data received from the listener.

1 like
linzeclark's avatar

Morning! well for me anyway.

So I'm so close, but I really need a bit of help of the 'laravel echo' bit. I've setup my Vue file and with pusher dummy data I can make it work at least. However when I try to add any echo code it just doesn't seem to pick up the pusher data.

<template>
    <div>
        <!-- Display notifications -->
        <div v-for="(notification, index) in notifications" :key="index">
            {{ notification.data.name }} - {{ notification.data.enquiry }}
        </div>

        <!-- Display unread count -->
        <p>Unread Count: {{ unreadCount }}</p>
    </div>
</template>

<script>
export default {
    data() {
        return {
            notifications: [], // Store received notifications
            unreadCount: 0, // Initialize unread count
        };
    },
    mounted() {
        // Simulate receiving a notification from Pusher
        const notificationData = {
            event: "App\Events\NewLeadEvent",
            channel: "admin_notifications",
            data: {
                name: "test test",
                enquiry: "Senior Dragons - Elite",
            },
        };

        // Handle the received notification
        this.handleNotification(notificationData);
    },
    methods: {
        handleNotification(notificationData) {
            if (notificationData.event === "App\Events\NewLeadEvent") {
                // Add the notification to the array
                this.notifications.push(notificationData);
                // Increment the unread count
                this.unreadCount++;
            }
        },
    },
};
</script>

So that worked but then when I changed my code to use echo listen it doesn't pick up the pusher data.

<template>
    <div>
        <!-- Display notifications -->
        <div v-for="(notification, index) in notifications" :key="index">
            {{ notification.name }} - {{ notification.enquiry }}
        </div>

        <!-- Display unread count -->
        <p>Unread Count: {{ unreadCount }}</p>
    </div>
</template>

<script>
import Echo from 'pusher-js';

export default {
    data() {
        return {
            notifications: [], // Store received notifications
            unreadCount: 0, // Initialize unread count
        };
    },
    mounted() {
        // Initialize Laravel Echo
        const echo = new Echo({
            broadcaster: 'pusher',
            key: import.meta.env.VITE_PUSHER_APP_KEY,
            cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'eu',
            encrypted: true,
        });

        // Subscribe to the channel
        echo.channel('admin_notifications').listen('NewLeadEvent', (event) => {
            // Handle the received notification
            this.handleNotification(event);
        });
    },
    methods: {
        handleNotification(notificationData) {
            // Add the notification to the array
            this.notifications.push(notificationData);
            // Increment the unread count
            this.unreadCount++;
        },
    },
};
</script>

Probably not helping that I'm a newbie to Vue. But I can't see where I'm going wrong here. Is It the handleNotification(notificationData) ?

Pusher :  : ["Event recd",{"event":"App\Events\NewLeadEvent","channel":"admin_notifications","data":{"name":"test test`","enquiry":"Senior Dragons - Elite"}}] logger.ts:19:21
Object { name: "test test`", enquiry: "Senior Dragons - Elite" }

Thanks

gych's avatar

Your code looks good but when you're learning vue I advise you to also learn vue composition api with script setup. Its not bad to know how options api work but most vue devs are switching to composition api. I also started with options api and switched to composition api.

I made an example of your code with composition API, I also added some console logs so you can see in the console if the data is correctly received by the listener and then added to the notifications.

Did you remove the initialization of Echo from your bootstrap js file? If it still doesn't work try to remove encrypted = true from the echo config.

<template>
    <div>
        <!-- Display notifications -->
        <div v-for="(notification, index) in notifications" :key="index">
            {{ notification.name }} - {{ notification.enquiry }}
        </div>

        <!-- Display unread count -->
        <p>Unread Count: {{ unreadCount }}</p>
    </div>
</template>

<script setup>
import { ref, onMounted } from "vue";
import Echo from "pusher-js";

const notifications = ref([]); // Store received notifications
const unreadCount = ref(0); // Initialize unread count

// Initialize Laravel Echo
const echo = new Echo({
    broadcaster: "pusher",
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? "eu",
    encrypted: true,
});

// Handle notification
const handleNotification = (notificationData) => {
    // Add the notification to the array
    notifications.value.push(notificationData);
    // Increment the unread count
    unreadCount.value++;

    // Log the notifications array
    console.log(notifications.value);

    // Log the unread count
    console.log(unreadCount.value);
};

// Subscribe to the channel
onMounted(() => {
    echo.channel("admin_notifications").listen("NewLeadEvent", (event) => {

        // Log the received notification
        console.log(event); 

        // Handle the received notification
        handleNotification(event);
    });
});
</script>
1 like
linzeclark's avatar

Thank you for your insight, I'll certainly have a look at learning Vue composition api going forward.

Thanks for your example hopefully I can get this up and running.

linzeclark's avatar

@gych Hi, yes I did thank you, and in the process figured out a few bits relating to Vue Components etc.

It needs a bit more work I suspect, but I have 2 Vue components one for the bell notification counts and another to show push notifications as they come in.

Notification counts - (I pass a variable in of existing database notifications to add to the push notifications)

<template>

    <!-- Only show the template if the sum of myVariable and unreadCount is greater than 0 -->
    <template v-if="myVariable + unreadCount > 0">
        <span class="alert-count">   {{ myVariable + unreadCount }}</span>
    </template>
</template>

<script setup>
// Vue Component to count pusher notifications and database notifications. MyVariables is importing php count of database notifications.
import {ref, onMounted} from "vue";


const notifications = ref([]); // Store received notifications
const unreadCount = ref(0); // Initialize unread count

defineProps({
    // receive a count from blade template of current unread notifications of user
    myVariable: Number
})

// Handle notification
const handleNotification = (notificationData) => {
    // Add the notification to the array
    notifications.value.push(notificationData);
    // Increment the unread count
    unreadCount.value++;

};

// listen for all events on this channel for admin notifications
onMounted(() => {
    window.Echo.private('private-admin-notify')
        .listenToAll((event, data) => {
            handleNotification(data);
        })
});
</script>

Then below the push notification items that add to any existing database notifications already pulled from the database in the blade view present in the dropdown. When the page refreshing it just updates to database notifications and counts. The only thing I got stuck on was trying to show the datetime in Vue as '1 min ago' etc DiffForHuman kind of thing. I can't get dayjs to work for me lol!

<template>

    <div v-for="notification in notifications" :key="notification.id">

        <a class="dropdown-item"  href="javascript:;">
            <div class="d-flex align-items-center">
                <div class="flex-grow">
                    <h6 class="msg-name text-dark"><i class="fadeIn animated bx bx-star text-warning"></i>    {{notification.adminPing.name}} </h6>
                    <p class="msg-info text-wrap px-4 mt-1" style="width: 20rem;">      {{notification.adminPing.message}}</p>
                    <span class="msg-time float-right mt-1 text-primary">Just now</span>
                </div>
            </div>
        </a>

    </div>
</template>

<script setup>
// Get pusher data of new notifications and list them in the vue template, on page refresh they will disappear but php will then list them.
import { ref, onMounted } from "vue";
import dayjs from "dayjs";


const notifications = ref([]); // Store received notifications

const handleNotification = (notificationData) => {
    // Add the notification to the array
    notifications.value.push(notificationData);
};

// listen for all events on this channel for admin notifications
onMounted(() => {
    window.Echo.private('private-admin-notify')
        .listenToAll((event, data) => {
            handleNotification(data);
        })
});
</script>

I'm sure there is a more elegant way to do this and maybe livewire might be a solution. But for now it kind of works so thanks for your help on this! I've learnt a few things! :)

gych's avatar

@linzeclark No problem, I'm glad you've already learnt some new things :)

What is the issue you're exactly having with dayjs? Is it giving you an error?

1 like
linzeclark's avatar

@gych I got the dayjs working. After sorting out my Stripe payments integrations, I think it helped me get my head around javascript/vue and I sorted it! Thanks for all your help!

Please or to participate in this conversation.