NativePHP for Laravel 13: Your Web App as a Windows, macOS, and Linux Application
Your Laravel app works in the browser and now you have been asked for "a desktop app": NativePHP packages your Laravel project as a native application for Windows, macOS, and Linux without rewriting the frontend. This guide walks through installation, your first window, and packaging.
What NativePHP Is and Why You Might Need It
NativePHP is an ecosystem for building desktop and mobile applications with Laravel: your Laravel app is served locally and a runtime opens it in a native window, while PHP code reaches operating system features through facades and classes. For the desktop, the current stable release is NativePHP desktop v2, with official documentation kept separate from the mobile docs because each platform has its own nuances.
The typical scenario is easy to recognize: the internal tool your team half-uses because someone has to open Chrome and type a URL, the app a client wants installed on their Mac or Windows machine, the dashboard that should notify users even when the tab is closed. Until a few years ago, meeting that requirement meant learning a new stack or maintaining a parallel desktop frontend. NativePHP attacks exactly that problem: you stay in Laravel.
Laravel as a Desktop Backend: The Same App, a New Window
The first thing to understand is that you are not building a new application: the same Laravel app, with its routes, controllers, Eloquent models, and queues, is served locally and shown inside a native window. Blade views or Livewire and Inertia components render exactly as they do in the browser, because the window technically loads your web app. What changes is the experience: the user sees a program with its own icon, menu, and system notifications instead of one more tab.
Read also
Under the Hood: The Runtime and the Native Window
To make that happen, NativePHP desktop v2 bundles a desktop runtime, Electron underneath, together with your application. You do not need to master Electron or write JavaScript glue code: NativePHP manages the runtime, and your code stays PHP. Think of it as a single-tab browser that always opens your app and that also lets you touch the operating system from Laravel.
When Moving Your Web App to the Desktop Makes Sense (and When It Does Not)
NativePHP shines when you need real system integration: application menus, native notifications that arrive even when the window is in the background, file dialogs, or persistent local storage. It also shines when you want to hand an installer to a client or your team without spinning up a second project. On the other hand, if the actual requirement is "quick access" or "works offline," an installable PWA or a good URL is usually enough; we cover that at the end, because it is the decision that saves the most time.
Requirements and Setup
What You Need: Laravel 13 with Modern PHP and Node/NPM
Start from a working Laravel 13 app. If you are coming from Laravel 12, check the Laravel 13 features and upgrade guide before jumping into the desktop. You also need a modern PHP, Composer 2, and Node with NPM, because NativePHP compiles assets with Vite just like your web app. No experience with Electron, Tauri, or native languages is required.
Installing the NativePHP Package and Publishing config/nativephp.php
composer require nativephp/desktop
php artisan native:installThe first command installs the NativePHP v2 desktop runtime package. The second one, php artisan native:install, is the installer: it publishes the service provider that bootstraps the Electron runtime dependencies and publishes the configuration file at config/nativephp.php.
What the Installation Adds to Your Project
Besides the configuration file, the installer publishes a service provider, the natural place to configure menus and windows, and adds a native:dev script to your composer.json. It also registers php artisan native:install as a post-update-cmd: after every composer update, your desktop environment stays in sync without you having to remember anything.
Your First Window: php artisan native:run
Running the App in Development as a Desktop Application
With the package installed, the development command is php artisan native:run: it starts your Laravel app locally and opens it inside the native window, showing your project home page. The official documentation gives sound advice: before opening the window, test the app in the browser, because startup exceptions are much easier to spot in a tab than inside the runtime.
Working with Blade, Livewire, or Inertia Inside the Window
Because the window loads your web app, your whole frontend works the same way. If you use Livewire 4 or Inertia 3 with Laravel 13, components keep responding inside the window; plain Blade or Alpine works too. There is no special layer to learn: what you see in the browser is what you see in the window.
Native Features from PHP
This is the best part of NativePHP: operating system features are called from PHP with facades, with no JavaScript bridge layer to maintain. The documentation groups the APIs by area (menus, windows, dialogs, notifications, clipboard, global hotkeys, system, and more); three or four of them cover most management applications.
The Application Menu with the Menu Facade
The application menu is configured in the boot method of the service provider published during installation, using the Menu facade. This example builds the standard macOS menus and opens the first window:
<?php
use Native\Desktop\Facades\Menu;
use Native\Desktop\Facades\Window;
public function boot(): void
{
Menu::create(
Menu::app(), // macOS only
Menu::file(),
Menu::edit(),
Menu::view(),
Menu::window(),
);
Window::open();
}If you do not need customization, Menu::default() builds the same set in a single line. For your own menus, the facade offers items such as Menu::link(), Menu::route(), or Menu::label(), which you can nest into submenus and bind to Laravel routes or external URLs.
System Notifications from a Controller
Sending a native notification is straightforward from any controller:
<?php
use Native\Desktop\Facades\Notification;
public function remind(): void
{
Notification::title('Task due soon')
->message('Check the list before 6:00 PM')
->show();
}Keep in mind the difference from Laravel notifications (database, mail, or queue based): this one is an operating system notification, the kind that appears in the macOS or Windows notification center even when your window is not focused. Perfect for alerting about a completed task, a finished backup, or a silent error.
File Dialogs and Other System APIs
To ask the user for a file or a folder, or to choose where to save a new one, you use the Dialog class:
<?php
use Native\Desktop\Dialog;
$path = Dialog::new()
->title('Select the CSV file')
->open();
The open() method returns the chosen path, or null if the user cancels, and save() returns the path where the user wants to save. That is the foundation for importing inventories, exporting reports, or opening a specific project from disk.
Secure Local Storage for Your App's Data
For preferences that must survive app restarts, such as the last opened folder, the selected theme, or a sync token, the Settings facade stores values in a config.json file inside the app data directory:
<?php
use Native\Desktop\Facades\Settings;
Settings::set('last_folder', $path);
$lastFolder = Settings::get('last_folder', '/');
With get() you can pass a default value, or a closure, when the key does not exist yet. It is the natural replacement for localStorage when you need persistence on the native side; for truly sensitive data, review the NativePHP security guide instead of storing secrets in plain text.
Packaging for Production: php artisan native:build
Building per Platform: The Installer for Each System
When the app is ready, php artisan native:build compiles your application together with the Electron runtime into a single executable for the platform where you run the command: it produces a .dmg on macOS, a setup.exe on Windows, and an AppImage or .deb on Linux, with the output artifacts in the project dist folder. You can also specify a target platform for cross-compilation, for example php artisan native:build win from a Mac. That said, the documentation recommends building and testing on each real operating system before shipping: a cross-build is not a substitute for a native test.
The App Config File: Name, Version, and Identifiers
Before compiling, review config/nativephp.php: there you define the app version (NATIVEPHP_APP_VERSION), the app_id as a reverse domain (NATIVEPHP_APP_ID), and other values the operating system uses to identify your program in the dock, the Start menu, or the installer. It is the binary's identity card: changing it later complicates updates, so it is worth getting right from the first build.
Secrets and .env Inside the Binary: What to Watch For
This is the point almost nobody mentions before you lose a weekend: when you package, the files in your project, including the .env, travel inside the binary. If your .env contains real production keys, such as database credentials, Stripe keys, or third-party API secrets, you would be shipping them to anyone who opens the installer. For a local desktop app, use a minimal .env with credentials generated for that deployment, never the same ones your production server uses. The official documentation has dedicated environment and security guides for this.
Honest Limitations and Alternatives
Size and Memory: The Runtime Ships Inside Your App
The price of not maintaining a second frontend is that the desktop runtime ships inside the binary: installers weigh considerably more than a web app and, while running, the application uses more memory than a browser tab. For internal tools that is usually irrelevant; for apps you distribute publicly, it is a fact you want to know before promising something lightweight.
Code Signing and Public Distribution
Public distribution has its own paperwork: macOS requires signing and notarization to avoid Gatekeeper warnings, and Windows shows SmartScreen when the executable is unsigned. For direct distribution outside the Mac App Store there are paths without a developer certificate, but the user will see warnings. If your plan is to sell or give away the app publicly, budget for code signing from the start instead of improvising at the end.
What If You Only Need a PWA or a URL?
The most honest question is whether you need NativePHP or just a better web app. An installable PWA opens from the desktop, works offline with a service worker, and weighs almost nothing; a well-placed URL on your team's dock also solves many "can you make me an app?" requests. NativePHP adds value when you genuinely need operating system integration, menus, native notifications, local files, or an installer to hand over, or when the client will not accept "open Chrome and type this." If that is not your case, save yourself the runtime.
Conclusion
The path from web to desktop with NativePHP is short: install the package, run native:install, start native:run, and in less than an hour you will be looking at your Laravel app inside a native window. Then add menus, notifications, and storage with PHP facades, and when the client asks for it, native:build leaves you with the installer for their platform. If tomorrow the requirement moves to mobile, NativePHP Mobile extends the same approach to iOS and Android by reusing the Laravel logic you already have. This blog keeps covering Laravel 13 with practical guides: if you want more, check out the web development articles.

