rohitshakya/laravel-beacon

Realtime notification UI for Laravel with topbar dropdown, inbox, and Livewire + Echo support. Fully customizable and production ready.
35 3
Install
composer require rohitshakya/laravel-beacon
Latest Version:2.0.3
PHP:^8.2
License:MIT
Last Updated:Apr 27, 2026
Links: GitHub  ·  Packagist
Maintainer: rohitshakyaa

🔔 Beacon

Realtime Notification UI for Laravel

Like a lighthouse in the dark, Beacon signals new activity to your users.

Beacon is a drop-in notification UI for Laravel that provides a topbar dropdown, inbox page, and realtime updates using Livewire and broadcasting. It is designed to be elegant, customizable, and easy to integrate into any Laravel app — and it works for any notifiable model, not just App\Models\User.


Why "Beacon"?

A beacon is a guiding signal — a lighthouse that alerts ships of activity and direction.

This package acts the same way:

  • Signals new notifications
  • Guides users to important updates
  • Works in realtime
  • Always visible from the topbar

Instead of silently storing notifications in the database, Beacon announces them to users through UI, events, and realtime broadcasting.


Features

  • Topbar notification dropdown
  • Full inbox page
  • Realtime updates (Echo / Reverb / Pusher)
  • Livewire powered UI
  • Multi-notifiable — works with User, Reseller, Employee, or any model that uses Laravel's Notifiable trait
  • Multiple topbars on the same page (each bound to its own private channel)
  • Fully customizable Blade views
  • Browser events for JS integrations
  • Plug-and-play config

📦 Installation

composer require rohitshakya/laravel-beacon

Publish config:

php artisan vendor:publish --tag=beacon-config

Publish views (optional):

php artisan vendor:publish --tag=beacon-views

Quick Usage

1. Add the topbar

The simplest case — render for the currently authenticated user:

<x-beacon::topbar />

Or mount the Livewire component directly:

<livewire:beacon.topbar />

Both fall back to auth()->user() when no notifiable is passed.

Render for any notifiable

Pass any model that uses the Notifiable trait:

{{-- For a Reseller --}}
<x-beacon::topbar :notifiable="$reseller" />

{{-- For an Employee --}}
<x-beacon::topbar :notifiable="$employee" />

Or via the Livewire tag with explicit class + id:

<livewire:beacon.topbar
    :notifiable-class="App\Models\Reseller::class"
    :notifiable-id="$reseller->id" />

The same applies to the inbox:

<x-beacon::inbox :notifiable="$reseller" />

{{-- or --}}
<livewire:beacon.inbox
    :notifiable-class="App\Models\Reseller::class"
    :notifiable-id="$reseller->id" />

Security note: the notifiableClass and notifiableId props on the Livewire components are marked #[Locked], so the client cannot mutate them after mount. The backend must decide which notifiable a given page may view.

Multiple topbars on one page

You can render multiple topbars side by side — for example, an admin dashboard showing a personal bell and a reseller-impersonation bell. Each one binds its own private Echo channel, so they update independently:

<x-beacon::topbar />                              {{-- current user --}}
<x-beacon::topbar :notifiable="$reseller" />      {{-- reseller, separate channel --}}

2. Send a notification

$user->notify(new SomeNotification());
$reseller->notify(new SomeNotification());
$employee->notify(new SomeNotification());

Beacon will automatically appear in UI for whoever the topbar is mounted for.


3. Listen in JS (optional)

window.addEventListener('beacon:notification', (e) => {
    console.log('New notification', e.detail);
});

Realtime Setup

Beacon supports:

  • Laravel Reverb
  • Pusher
  • Soketi
  • Ably
  • Any Echo driver

Make sure Echo is running. You do not need to call Echo.private(...) yourself — the topbar view binds the resolved channel for you.

The channel name is derived from the notifiable's class FQCN by default:

Notifiable Channel
App\Models\User with id 1 App.Models.User.1
App\Models\Reseller with id 5 App.Models.Reseller.5
App\Models\Employee with id 9 App.Models.Employee.9

This matches Laravel's built-in Notifiable::receivesBroadcastNotificationsOn() convention, so no custom routing is needed on the broadcaster.


Configuration

config/beacon.php

return [

    'views' => [
        'inbox' => 'beacon::inbox.default',
        'item'  => 'beacon::item.default',
    ],

    'topbar' => [
        'limit' => 8,
    ],

    'realtime' => [
        'enabled'         => true,
        'resolver'        => \RohitShakya\Beacon\Support\DefaultChannelResolver::class,
        'channel_pattern' => '{class}.{id}',
        'channels'        => [
            // \App\Models\User::class => 'App.Models.User.{id}',
        ],
        'browser_event'   => 'beacon:notification',
    ],

    'notifications' => [
        // \App\Notifications\InvoicePaid::class => [...],
    ],
];

Config Options Explained

Views

Override the inbox and per-item Blade templates.

'views' => [
    'inbox' => 'beacon::inbox.default',
    'item'  => 'beacon::item.default',
],

The topbar view is the Livewire view (beacon::livewire.topbar) — publish it with php artisan vendor:publish --tag=beacon-views and edit resources/views/vendor/beacon/livewire/topbar.blade.php if you want to customize it.


Topbar

'topbar' => [
    'limit' => 8,
],
Option Description
limit notifications shown in dropdown

Realtime

'realtime' => [
    'enabled'         => true,
    'resolver'        => \RohitShakya\Beacon\Support\DefaultChannelResolver::class,
    'channel_pattern' => '{class}.{id}',
    'channels'        => [
        // \App\Models\User::class => 'App.Models.User.{id}',
    ],
    'browser_event'   => 'beacon:notification',
],
Option Description
enabled Enable Echo listening
resolver FQCN that resolves the broadcast channel for a given notifiable (see Custom Channel Resolver)
channel_pattern Default pattern used by DefaultChannelResolver. {class} is replaced with the FQCN (\\.); {id} with the key
channels Per-class overrides keyed by FQCN. Wins over channel_pattern when the notifiable matches
browser_event Event fired in the browser

Per-class override example

'realtime' => [
    'channels' => [
        \App\Models\Reseller::class => 'tenants.{id}.reseller',
        \App\Models\Employee::class => 'org.employee.{id}',
    ],
],

Custom Channel Resolver

Beacon resolves the broadcast channel server-side for the notifiable that the topbar/inbox is rendered for, then ships the channel name to the frontend through the Livewire props. The browser never has to figure out the channel name — and you can derive the channel from anything you have access to in PHP (tenant, organization, custom user key, etc.).

1. Implement the contract

namespace App\Beacon;

use RohitShakya\Beacon\Support\Contracts\ChannelResolver;

class TenantChannelResolver implements ChannelResolver
{
    public function resolve($notifiable = null): ?string
    {
        $notifiable = $notifiable ?: auth()->user();
        if (! $notifiable) {
            return null;
        }

        // Whatever runtime logic you need — DB lookup, tenant scope, etc.
        return sprintf(
            'tenants.%s.%s.%s',
            tenant()->id,
            str_replace('\\', '.', $notifiable::class),
            $notifiable->getKey(),
        );
    }
}

Return null when there is no notifiable; the frontend will skip Echo binding cleanly.

2. Point the config at it

// config/beacon.php
'realtime' => [
    'enabled'  => true,
    'resolver' => \App\Beacon\TenantChannelResolver::class,
    // channel_pattern / channels are ignored — your resolver controls the name
],

The resolver is a string FQCN, so php artisan config:cache continues to work.

3. That's it

The package's topbar view receives the resolved channel as a Livewire prop and passes it to window.beaconTopbarBind(channel, eventName) automatically. You do not need to call beaconTopbarBind from your own app.js.


Customizing UI

Publish views:

php artisan vendor:publish --tag=beacon-views

Then edit:

resources/views/vendor/beacon/

You can redesign everything.


Browser Events

Beacon dispatches:

beacon:notification

Example:

window.addEventListener('beacon:notification', e => {
    toast(e.detail.title)
})

When multiple topbars are mounted (different notifiables), each one binds its own private channel and fires the same event — e.detail carries the notification payload as it arrived from Echo.


Testing Notifications

$user->notify(new TestNotification());
$reseller->notify(new TestNotification());

Open two tabs → watch realtime.


Use Cases

  • SaaS dashboards (per-user bell)
  • Admin panels (impersonate / view another notifiable's bell)
  • HR systems (employee notifications)
  • CRM (account manager + contact notifications)
  • ISP / reseller panels (reseller-scoped bells)
  • Any Laravel app needing notifications

Contributing

PRs welcome.

git clone
composer install
npm install

License

MIT


Author

Built with ❤️ for Laravel ecosystem.