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.
A beacon is a guiding signal — a lighthouse that alerts ships of activity and direction.
This package acts the same way:
Instead of silently storing notifications in the database, Beacon announces them to users through UI, events, and realtime broadcasting.
User, Reseller, Employee, or any model that uses Laravel's Notifiable traitcomposer require rohitshakya/laravel-beacon
Publish config:
php artisan vendor:publish --tag=beacon-config
Publish views (optional):
php artisan vendor:publish --tag=beacon-views
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.
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
notifiableClassandnotifiableIdprops 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.
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 --}}
$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.
window.addEventListener('beacon:notification', (e) => {
console.log('New notification', e.detail);
});
Beacon supports:
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.
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 => [...],
],
];
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' => [
'limit' => 8,
],
| Option | Description |
|---|---|
| limit | notifications shown in dropdown |
'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 |
'realtime' => [
'channels' => [
\App\Models\Reseller::class => 'tenants.{id}.reseller',
\App\Models\Employee::class => 'org.employee.{id}',
],
],
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.).
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.
// 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.
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.
Publish views:
php artisan vendor:publish --tag=beacon-views
Then edit:
resources/views/vendor/beacon/
You can redesign everything.
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.
$user->notify(new TestNotification());
$reseller->notify(new TestNotification());
Open two tabs → watch realtime.
PRs welcome.
git clone
composer install
npm install
MIT
Built with ❤️ for Laravel ecosystem.
How can I help you explore Laravel packages today?