Tilly The Coder
Tilly The Coder
NativePHP Desktop + Livewire 3: Click a Button, Fire a Native Notification
Tilly The Coder

NativePHP Desktop + Livewire 3: Click a Button, Fire a Native Notification

Tilly The Coder
6 mins
This is the estimated time it takes to read the article.

Reconstructed tutorial, written September 26, 2026. The historical five-minute URL is preserved, but setup time depends on your machine. This newly written example targets an existing Laravel application with Livewire 3 and NativePHP Desktop 2; it does not assume their APIs match the mobile package.

The goal is deliberately small: press a button in the desktop window and see a system notification. That gives you a concrete way to confirm the path from a Livewire request to PHP and then to the operating system before attaching it to a longer background task.

The NativePHP Desktop installation guide uses nativephp/desktop and native:install. In an existing project, check its dependency constraints before installing or upgrading either framework.

composer require nativephp/desktop:^2.0
php artisan native:install

Put the native call in one action

Create app/Actions/SendDesktopTestNotification.php. Keeping the system interaction in an action gives the component one clear responsibility and makes it easier to replace the test notification with a real completion event later.

namespace App\Actions;

use Native\Desktop\Facades\Notification;

final class SendDesktopTestNotification
{
    public function handle(): void
    {
        Notification::title('Desktop connection verified')
            ->message('Your Livewire action reached NativePHP.')
            ->show();
    }
}

The facade namespace and fluent methods come from NativePHP’s notification documentation. This sends an operating-system notification. The message is intentionally generic so the demonstration does not put account data on a lock screen.

Connect the Livewire component

Create app/Livewire/DesktopNotificationDemo.php. Livewire resolves the action dependency when the public method is invoked.

namespace App\Livewire;

use App\Actions\SendDesktopTestNotification;
use Illuminate\Contracts\View\View;
use Livewire\Component;

final class DesktopNotificationDemo extends Component
{
    public function send(SendDesktopTestNotification $notification): void
    {
        $notification->handle();
    }

    public function render(): View
    {
        return view('livewire.desktop-notification-demo');
    }
}

Add resources/views/livewire/desktop-notification-demo.blade.php with a real button and a visible pending state:

<div>
    <button type="button" wire:click="send"
        wire:loading.attr="disabled" wire:target="send">
        Send test notification
    </button>
    <p wire:loading wire:target="send" role="status">
        Sending notification…
    </p>
</div>

Mount <livewire:desktop-notification-demo /> inside the Blade page your desktop window already opens. That page must load Livewire through your application’s normal layout. Run the installed native:dev Composer script and use the resulting desktop window.

The component follows Livewire 3 actions. If you later accept an account ID or another public parameter, validate and authorize it inside the action. A disabled button cannot provide server-side authorization.

Test the component boundary without claiming native delivery

The desktop banner belongs to the operating system, but the component’s behavior can still have a focused automated check. Keep that distinction explicit in the test name and in your release notes. This PHPUnit test verifies that clicking the Livewire action calls the notification service once. It does not inspect the native process or a notification center.

Keep the final action intact. The following test mocks the Notification facade used by the real action, allowing the component and action to execute together. Place it in tests/Feature/Livewire/DesktopNotificationDemoTest.php:

namespace Tests\Feature\Livewire;

use App\Livewire\DesktopNotificationDemo;
use Livewire\Livewire;
use Native\Desktop\Facades\Notification;
use Tests\TestCase;

final class DesktopNotificationDemoTest extends TestCase
{
    public function test_it_requests_a_desktop_notification(): void
    {
        Notification::shouldReceive('title')
            ->once()
            ->with('Desktop connection verified')
            ->andReturnSelf();
        Notification::shouldReceive('message')
            ->once()
            ->with('Your Livewire action reached NativePHP.')
            ->andReturnSelf();
        Notification::shouldReceive('show')->once()->andReturnSelf();

        Livewire::test(DesktopNotificationDemo::class)
            ->call('send')
            ->assertHasNoErrors();
    }
}

Livewire documents component calls and assertions in its version 3 testing guide. If this test fails, inspect the action wiring and installed package API first. If it passes while no banner appears, continue with the runtime and operating-system checks below. Those results answer different questions.

Keep notification wording truthful

The button says “Send test notification” because it requests a notification. Avoid showing “Notification delivered” immediately after the PHP method returns: that would imply evidence the component does not have. A system may accept the request while focus settings suppress its banner. If you add a completion message, describe the completed application action precisely, such as “Notification requested”.

Verify the native result

Click once and look for the operating-system banner or notification center entry. A successful Livewire response proves only that the request completed. Capture the notification together with enough of the application window to identify the build being tested.

If nothing appears, first inspect the application error log and confirm the request ran in the NativePHP desktop runtime. Then check the application’s notification permissions and the operating system’s focus settings. Repeat with those settings understood; do not add a web toast and treat it as proof that the native notification worked.

Use a repeatable diagnosis sequence

  • No visible loading state: confirm the component is mounted and Livewire assets are loaded by the layout.

  • A failed Livewire request: read the exception and check the imported Desktop facade namespace against the installed package.

  • A successful request with no system entry: inspect desktop runtime logs and the application’s notification permission settings.

  • A notification center entry with no banner: record focus mode and banner preferences before changing application code.

  • Development works but the packaged build differs: repeat the check using the packaged application identity and its permissions.

Change one condition at a time. For example, run the same build with focus mode enabled and disabled, recording both observations. Reinstalling packages, changing the notification text, and changing system settings in one attempt can make a successful result impossible to explain or reproduce.

Move from a demonstration to a feature

A useful production notification announces an event the person cares about, such as a completed export. Trigger it only after the export is actually available, use a concise message, and provide an in-app way to find the result even if the person dismissed the banner.

Example: announce a completed export

Suppose an export workflow produces a CSV file. The sequence should be: validate the export request, generate the file, confirm that it exists at the intended location, update the export record, and then request a notification. The notification should describe the completed export rather than announce completion when the work was merely queued.

Keep the filename and download or open action in the application’s export history. That history is part of the feature’s normal interface, so users can return to their work after dismissing a banner. If the export runs outside the desktop process, establish how completion reaches the running desktop application before adding a native call to a server worker; a worker does not automatically have the user’s desktop runtime.

For a personal reminder, the timing requirement is different: the application needs to know whether it will be running at the chosen time. Do not infer scheduling support from this immediate notification example. Define and test that lifecycle separately, including an application restart and the machine sleeping through the reminder time.

Test repeated clicks, disabled notifications, and the packaged application. Keep the screenshot, operating-system version, dependency versions, and observed result together. That small record makes platform-specific behavior much easier to diagnose when the next release changes the native runtime.