Want the quickest possible path from “fresh Laravel app” to “my app shows a real OS notification”? Here’s a tiny, production-grade starting point that pairs NativePHP (Electron runtime) with Livewire 3. We’ll install the bits, add a single Livewire component, and wire up a button that triggers a native desktop notification—no custom JS required.
Along the way we’ll note a few Livewire-3 specifics (like the unified dispatch()
API replacing dispatchBrowserEvent()
in v2) and mention the modern Livewire starter kit that ships with Flux UI.
A Livewire component at /native-notify
with one button: “Show Native Notification”
When clicked (inside the NativePHP shell), it uses NativePHP’s Notification
facade to display a real OS notification.
Heads-up: Native notifications appear when you run the app in the native shell (
php artisan native:serve
). In a plain browser tab you won’t see the OS notification.
Photo of the application we will build.
PHP 8.3+, Laravel 11/12+, Node 22+ (NativePHP recommends modern local PHP & Node; Laravel 12 works great).
An existing Laravel app or the official Laravel Starter Kit (Livewire flavour) which includes Flux UI out of the box (optional).
composer require livewire/livewire
Livewire auto-injects its assets by default when a component is on the page. If you prefer manual control, you can use @livewireStyles
/@livewireScripts
, or switch to ESM with @livewireScriptConfig
+ Vite—see docs for the exact layout variants.
composer require nativephp/electron
php artisan native:install
The installer publishes config/nativephp.php
, registers the provider, and sets up the runtime. Run the app in a native window with:
php artisan native:serve
(You can use -vv
for verbose logs during development.)
php artisan make:livewire NativeNotifier
We’ll wire this component to a route so it renders as a full page (Livewire 3 supports routing directly to a component class).
Paste these in as-is. If a file already exists in your app, replace its contents with the version below.
<?php
declare(strict_types=1);
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Native\Laravel\Facades\Window;
class NativeAppServiceProvider extends ServiceProvider
{
/**
* Executed once the native application has been booted.
* Use this method to open windows, register global shortcuts, menus, etc.
*/
public function boot(): void
{
// Open a single app window and point it at our route.
Window::open()
->title(config('app.name', 'Laravel + NativePHP'))
->width(1100)
->height(740)
->centerOnScreen()
->url(route('native-notify'));
}
}
(This provider is published by native:install
; we’re just giving it a sensible default window.)
<?php
declare(strict_types=1);
namespace App\Livewire;
use Livewire\Component;
use Native\Laravel\Facades\Notification;
class NativeNotifier extends Component
{
public string $title = 'Hello from NativePHP';
public string $message = 'This is a desktop notification from your Laravel app.';
public function send(): void
{
// Trigger a real OS notification (NativePHP desktop).
Notification::title($this->title)
->message($this->message)
->show();
// Optional: In Livewire 3, browser events were unified into `dispatch()`.
// If you want to react on the page, you might do:
// $this->dispatch('notify-sent');
}
public function render()
{
return view('livewire.native-notifier');
}
}
(NativePHP’s Notification facade shows true system notifications.)
<div style="max-width: 620px; margin: 2rem auto; font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;">
<h1 style="font-size: 1.5rem; font-weight: 700; margin-bottom: .75rem;">
Native Notification (Livewire 3 + NativePHP)
</h1>
<p style="margin-bottom: 1rem; color: #6b7280;">
Click the button to show a real OS notification. Run the app with
<code>php artisan native:serve</code>.
</p>
<div style="display: grid; gap: .75rem; margin-top: 1rem;">
<label style="font-weight: 600;">Title</label>
<input
type="text"
wire:model.live="title"
placeholder="Notification title"
style="padding: .6rem .8rem; border: 1px solid #e5e7eb; border-radius: .5rem;"
>
<label style="font-weight: 600; margin-top: .5rem;">Message</label>
<textarea
wire:model.live="message"
rows="3"
placeholder="Notification body"
style="padding: .6rem .8rem; border: 1px solid #e5e7eb; border-radius: .5rem; resize: vertical;"
></textarea>
<button
wire:click="send"
style="
margin-top: .75rem;
padding: .7rem 1rem;
border-radius: .6rem;
border: 1px solid #111827;
background: #111827;
color: white;
font-weight: 600;
cursor: pointer;
"
>
Show Native Notification
</button>
</div>
@script
<script>
// Optional Livewire-3 event example (unified event system).
// Listen for `$this->dispatch('notify-sent')` from the component:
Livewire.on('notify-sent', () => {
console.log('Notification dispatched');
});
</script>
@endscript
</div>
Livewire 3 no longer uses
dispatchBrowserEvent()
—the unified way is$this->dispatch('event-name')
in PHP andLivewire.on('event-name', ...)
in JS, or$dispatch
in the template.
<?php
//web.php
use Illuminate\Support\Facades\Route;
use App\Livewire\NativeNotifier;
Route::get('/native-notify', NativeNotifier::class)->name('native-notify');
Route::get('/', function () {
return redirect()->route('native-notify');
});
(Routing a page directly to the component class is idiomatic Livewire 3.)
//app.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>{{ $title ?? config('app.name', 'Laravel') }}</title>
</head>
<body>
{{ $slot }}
{{-- Livewire auto-injects assets when a component is on the page.
If you prefer manual/ESM control, see the installation docs. --}}
</body>
</html>
(This mirrors Livewire’s quickstart layout—simple and works out of the box. You can switch to manual asset modes later if needed.)
Start NativePHP: php artisan native:serve
The native window opens on /native-notify
(we set that in the provider).
Click Show Native Notification → you’ll get a real OS notification.
Unified events: emit()
and dispatchBrowserEvent()
(v2) are gone; use $this->dispatch('event-name', key: 'value')
and listen with Livewire.on(...)
, or $dispatch
inside templates.
Assets: Livewire auto-injects by default; you can opt into manual tags or ESM bundling with @livewireScriptConfig
if integrating with custom Alpine plugins, etc.
Starting from the Laravel Starter Kit → Livewire option gives you a modern app shell with Flux UI components prewired (Tailwind v4, auth scaffolding, settings, etc.). If you did that, you can swap the button to Flux like so:
<x-flux::button wire:click="send" appearance="primary">
Show Native Notification
</x-flux::button>
(The rest of the files remain the same.)
No JS glue for notifications: we call a PHP facade that talks directly to the OS via NativePHP.
Livewire 3 ergonomics: simple route-to-component, unified events, optional ESM.
Easy to extend: Add menus, dialogs, global shortcuts, or alerts (Alert
facade) without leaving PHP.
NativePHP – Installation & Notifications (desktop v1).
Livewire 3 – Installation, Quickstart & Events (routing to components, asset strategy, unified dispatch).
Laravel Starter Kits (Livewire option ships with Flux UI).
That’s it—you’ve got a clean, minimal NativePHP + Livewire 3 seed that shows a real desktop notification. From here, you can add Flux UI modals, menus, or even hook notification clicks to open specific windows or routes.