Desktop cookbook
Recipes are deliberately small. Each one shows the shortest production-shaped path, then points to the guide that explains the full contract.
Call typed PHP from JavaScript
Section titled “Call typed PHP from JavaScript”#[Command('reports.generate')]public function generate(int $accountId, ReportService $reports): ReportResource{ return new ReportResource($reports->generate($accountId));}const report = await window.pam.invoke("reports.generate", { accountId: 42 });Cancel work when the screen goes away
Section titled “Cancel work when the screen goes away”const controller = new AbortController();
const request = window.pam.invoke( "reports.generate", { accountId: 42 }, { timeout: 10_000, signal: controller.signal },);
window.addEventListener("pagehide", () => controller.abort(), { once: true });const report = await request;Cancellation is forwarded to the host. The interrupted command is never automatically replayed.
Emit a typed event
Section titled “Emit a typed event”final readonly class ExportCompleted{ public function __construct( public int $exportId, public string $filename, ) {}}
$events->emit(new ExportCompleted($export->id, $export->filename));const off = window.pam.on("export.completed", ({ filename }) => { console.log(`Ready: ${filename}`);});
// Call off() when the listener is no longer needed.Open or target another window
Section titled “Open or target another window”#[Window(id: 'settings', title: 'Settings', source: 'resources/settings.html')]final class SettingsWindow extends DesktopWindow {}
#[Command]public function settings(SettingsWindow $settings): void{ $settings->show(); $settings->focus();}Work with authorized files
Section titled “Work with authorized files”$permissions->filesystem( 'documents', __DIR__.'/../storage/documents', read: true, write: true,);await window.pam.fs.writeText( { root: "documents", path: "draft.md" }, "# Draft",);const selected = await window.pam.dialog.openFile({ filters: [{ name: "Markdown", extensions: ["md"] }],});
if (selected) { const source = await window.pam.fs.readText(selected);}The dialog returns an opaque grant, not unrestricted filesystem access.
Run periodic PHP work
Section titled “Run periodic PHP work”protected function jobs(): array{ return [SyncDocuments::class];}Use background jobs for bounded periodic work that belongs to the application lifecycle. Use an isolated Rust plugin for native work that needs a separate process and versioned exports.
Build every supported artifact
Section titled “Build every supported artifact”pam desktop doctorpam desktop build --format allThe build validates the manifest and creates integrity metadata. Debian output
requires dpkg-deb; the portable archive includes per-user install and
uninstall scripts.
Find the complete contract
Section titled “Find the complete contract”| Need | Guide |
|---|---|
| DTOs, enums, command classes, return values | Commands & DI |
| Named windows, typed events, menus, tray | Windows, events & menus |
| Files, SQLite, HTTP, dialogs, clipboard | Native capabilities |
| Periodic work and overlap policy | Background jobs |
| PHP and process-isolated Rust extensions | Plugins |
| Archives, Debian packages, integrity | Distribution |