| name | nativephp-desktop-development |
| description | Develops NativePHP Desktop v2 applications. Activates when working with desktop windows, menu bars, system tray, dialogs, alerts, notifications, global shortcuts, clipboard, child processes, auto-updater, or native OS features; or when the user mentions NativePHP, desktop app, native, window management, tray, system integration, or building/publishing desktop apps. |
NativePHP Desktop v2 Development
NativePHP Desktop builds cross-platform desktop apps using Laravel, powered by Electron.
When to Apply
Activate this skill when:
- Working with the
NativeAppServiceProvider boot method
- Managing windows, menu bars, or system tray
- Using dialogs, alerts, or native notifications
- Registering global shortcuts or clipboard operations
- Working with system APIs (encryption, TouchID, printing, theme detection)
- Setting up broadcasting between PHP and JavaScript
- Configuring databases, files, queues, or child processes for desktop
- Building, publishing, or configuring auto-updates
- Writing tests that use NativePHP facade fakes
Documentation
Use search-docs for version-specific NativePHP documentation. For topics not found there, use WebFetch on the URLs in references/available-docs.md.
Namespace & Environment
| Item | Value |
|---|
| PHP namespace | Native\Desktop |
| Facades | Native\Desktop\Facades\* |
| Events | Native\Desktop\Events\* |
| Config file | config/nativephp.php |
| Service provider | App\Providers\NativeAppServiceProvider |
| Run command | php artisan native:run |
| Dev with Vite | composer native:dev |
| Build command | php artisan native:build |
Important: The namespace is Native\Desktop, NOT Native\Laravel (v1 legacy).
Application Lifecycle
The NativeAppServiceProvider bootstraps all native features in its boot() method:
use Native\Desktop\Facades\Window;
use Native\Desktop\Contracts\ProvidesPhpIni;
class NativeAppServiceProvider implements ProvidesPhpIni
{
public function boot(): void
{
Window::open()
->width(1200)
->height(800)
->rememberState();
}
public function phpIni(): array
{
return [
'memory_limit' => '512M',
];
}
}
Boot sequence: Electron starts → php artisan migrate → php artisan serve → boot() runs → ApplicationBooted event dispatches.
Windows
Facade: Native\Desktop\Facades\Window
Opening & Managing
Window::open('main');
Window::close('main');
Window::current();
Window::all();
Window::resize(800, 600, 'main');
Window::minimize('main');
Window::maximize('main');
Chainable Configuration
| Category | Methods |
|---|
| Size | width(), height(), minWidth(), minHeight(), maxWidth(), maxHeight() |
| Position | position($x, $y) |
| State | minimized(), maximized(), fullscreen(), rememberState() |
| Behavior | resizable(), movable(), minimizable(), maximizable(), closable(), focusable(), alwaysOnTop() |
| Navigation | url(), route(), preventLeaveDomain(), preventLeavePage(), suppressNewWindows() |
| Appearance | title(), titleBarHidden(), trafficLightsHidden(), backgroundColor(), hasShadow(), transparent(), vibrancy(), hideMenu(), skipTaskbar() |
| Dev | showDevTools(), zoomFactor(), webPreferences([]) |
Window Events
All events broadcast on the nativephp channel under Native\Desktop\Events\Windows\*:
WindowShown, WindowClosed, WindowFocused, WindowBlurred, WindowMinimized, WindowMaximized, WindowResized (includes width/height payload).
Menu Bar (System Tray)
Facade: Native\Desktop\Facades\MenuBar
MenuBar::create()
->route('home')
->width(400)
->height(500)
->resizable()
->alwaysOnTop()
->vibrancy('light')
->withContextMenu(
Menu::new()->item('Quit', 'quit')
);
MenuBar::show();
MenuBar::hide();
MenuBar::label('3 tasks');
MenuBar::tooltip('My App');
Standalone menu bars auto-hide the dock icon. Use showDockIcon() to keep it visible. Icon should be 22x22px (44x44px for retina with @2x naming).
Events: MenuBarShown, MenuBarHidden, MenuBarContextMenuOpened.
Application
Facade: Native\Desktop\Facades\App
| Method | Description |
|---|
App::quit() | Quit application |
App::relaunch() | Quit and relaunch |
App::focus() | Focus app window |
App::hide() | Hide all windows (macOS) |
App::isHidden() | Check hidden state (macOS) |
App::version() | From config/nativephp.php |
App::getLocale() | e.g. "fr-FR" |
App::badgeCount(5) | Set dock badge (macOS/Linux) |
App::addRecentDocument($path) | Recent files (macOS/Win) |
App::openAtLogin(true) | Auto-start (macOS/Win) |
Dialogs & Alerts
Dialogs
Facade: Native\Desktop\Facades\Dialog
$file = Dialog::new()
->title('Select Image')
->filter('Images', ['jpg', 'png', 'gif'])
->multiple()
->open();
$path = Dialog::new()
->title('Save As')
->defaultPath(storage_path())
->save();
$folder = Dialog::new()->folders()->open();
Config: title(), button(), defaultPath(), filter(), multiple(), withHiddenFiles(), asSheet(), folders().
Alerts
Facade: Native\Desktop\Facades\Alert
$clicked = Alert::new()
->title('Confirm')
->buttons(['Yes', 'No', 'Cancel'])
->type('question')
->defaultId(0)
->show('Are you sure?');
Types: none, info, warning, error, question.
Settings
Facade: Native\Desktop\Facades\Settings
Persistent key-value store in config.json within appdata. Survives restarts.
Settings::set('theme', 'dark');
Settings::get('theme', 'light');
Settings::forget('theme');
Settings::clear();
Event: Native\Desktop\Events\Notifications\SettingChanged — has $event->key and $event->value.
Notifications
Facade: Native\Desktop\Facades\Notification
Notification::title('Download Complete')
->message('Your file is ready.')
->sound('Ping')
->event(DownloadClicked::class)
->addAction('Open File')
->hasReply('Type a reply...')
->show();
Events: NotificationClicked, NotificationClosed, NotificationReply (macOS), NotificationActionClicked (includes $index).
Global Shortcuts
Facade: Native\Desktop\Facades\GlobalShortcut
Register in NativeAppServiceProvider::boot():
GlobalShortcut::key('CmdOrCtrl+Shift+A')
->event(MyShortcutEvent::class)
->register();
GlobalShortcut::key('CmdOrCtrl+Shift+A')->unregister();
Modifiers: Command/Cmd, Control/Ctrl, CmdOrCtrl, Alt, Option, Shift, Super, Meta.
Utility Facades
| Facade | Key Methods |
|---|
Clipboard | text($val) read/write, html($val) read/write, image($path) read/write, clear() |
Shell | openExternal($url), showInFolder($path), openFile($path), trashFile($path) |
Screen | displays() → array of display objects, cursorPosition() → {x, y} |
System | canEncrypt(), encrypt($data), decrypt($data), promptTouchID($reason) (macOS), print($html), printToPDF($html), printers(), timezone(), theme() |
PowerMonitor | getSystemIdleState($threshold), getSystemIdleTime(), getCurrentThermalState(), isOnBatteryPower() |
PowerMonitor events: PowerStateChanged, SpeedLimitChanged, ThermalStateChanged.
Broadcasting (PHP ↔ JS)
PHP → JS: Broadcast events
Events must implement ShouldBroadcastNow (not ShouldBroadcast) on the nativephp channel:
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
class JobFinished implements ShouldBroadcastNow
{
public function broadcastOn(): array
{
return [new Channel('nativephp')];
}
}
JS: Listen to events
Must be inside the native:init handler to ensure the Native object exists:
window.addEventListener('native:init', () => {
Native.on('Native\\Desktop\\Events\\Windows\\WindowBlurred', (payload, event) => {
});
});
Livewire Integration
#[On('native:'.WindowFocused::class)]
public function windowFocused() { ... }
Database & Files
Database
SQLite only — the only supported database engine for bundled desktop apps.
- NativePHP auto-switches to SQLite during builds
- Database created in
appdata directory
- Version changes in
config/nativephp.php trigger automatic migrations on user machines
| Command | Purpose |
|---|
php artisan native:migrate | Run migrations in desktop database |
php artisan native:migrate:fresh | Destructive refresh |
php artisan native:seed | Run seeders |
Files & Storage
NativePHP rewrites storagePath() to Electron's appdata. Pre-configured Storage disks:
user_home, desktop, documents, downloads, music, pictures, videos, recent, extras
Storage::disk('desktop')->get('file.txt');
Storage::disk('extras')->get('my-binary');
Bundle extra files by placing them in extras/ at project root.
Queues & Child Processes
Queues
Facade: Native\Desktop\Facades\QueueWorker
Jobs stored in SQLite. A default worker starts automatically. Configure in config/nativephp.php:
'queue_workers' => [
'default' => [
'queues' => ['default'],
'memory_limit' => 128,
'timeout' => 60,
'sleep' => 3,
],
],
Manual control: QueueWorker::up($config), QueueWorker::down($alias).
Child Processes
Facade: Native\Desktop\Facades\ChildProcess
ChildProcess::start(cmd: 'tail -f storage/logs/laravel.log', alias: 'tail');
ChildProcess::php('path/to/script.php', alias: 'script');
ChildProcess::artisan('smtp:serve', alias: 'smtp');
ChildProcess::node('resources/js/watcher.js', alias: 'watcher');
ChildProcess::start(cmd: $cmd, alias: 'bg', persistent: true, cwd: storage_path());
ChildProcess::get('tail');
ChildProcess::stop('tail');
ChildProcess::restart('tail');
ChildProcess::message('Hello', 'tail');
Events: ProcessSpawned ($alias, $pid), ProcessExited ($alias, $code), MessageReceived ($alias, $data), ErrorReceived ($alias, $data).
Building & Publishing
| Command | Purpose |
|---|
php artisan native:build | Build for current platform |
php artisan native:build mac | Cross-compile for macOS |
php artisan native:build win | Cross-compile for Windows (needs Wine on Linux) |
php artisan native:build linux | Cross-compile for Linux |
php artisan native:publish | Build + upload to updater provider |
Pre/post hooks in config/nativephp.php: 'prebuild' => ['npm run build'].
Code signing: macOS requires Apple ID + notarization. Windows uses Azure Trusted Signing.
Auto-updater: Configure updater in config/nativephp.php. Providers: github, s3, spaces. Manual control:
use Native\Desktop\Facades\AutoUpdater;
AutoUpdater::checkForUpdates();
AutoUpdater::quitAndInstall();
Update events: CheckingForUpdate, UpdateAvailable, UpdateNotAvailable, DownloadProgress, UpdateDownloaded, Error.
Testing
All NativePHP facades support faking for tests:
| Facade | Fake | Key Assertions |
|---|
Window | Window::fake() | assertOpened($id), assertClosed($id), assertHidden($id) |
GlobalShortcut | GlobalShortcut::fake() | assertKey($combo), assertRegisteredCount($n), assertEvent($class) |
Shell | Shell::fake() | assertOpenedExternal($url), assertShowInFolder($path), assertOpenedFile($path), assertTrashedFile($path) |
PowerMonitor | PowerMonitor::fake() | assertGetSystemIdleState(), assertGetSystemIdleStateCount($n) |
ChildProcess | ChildProcess::fake() | assertStarted($alias), assertPhp(), assertArtisan(), assertNode(), assertStop($alias), assertMessage($callback) |
QueueWorker | QueueWorker::fake() | assertUp($callback), assertDown($alias) |
use Native\Desktop\Facades\Window;
it('opens the settings window', function () {
Window::fake();
$this->get('/open-settings');
Window::assertOpened('settings');
});
Common Pitfalls
- Using
Native\Laravel namespace instead of Native\Desktop (v1→v2 migration change)
- Putting secrets in
.env — the entire file gets bundled into the app. Use cleanup_env_keys in config
- Using
env() directly instead of config() — standard Laravel rule, doubly important for desktop
- Forgetting
native:migrate — separate from regular migrate, operates on the desktop SQLite database
- Trying to use MySQL/Postgres — only SQLite works in bundled desktop apps
- Using
ShouldBroadcast instead of ShouldBroadcastNow for native events
- Placing JS event listeners outside
native:init handler — Native object doesn't exist yet
- Not bumping version in
config/nativephp.php before building — migrations won't run on user machines
- Building without code signing credentials — macOS will show "damaged app" warnings