Originally published on php-apps.learnio.dev — part of the Using Native PHP course. View this lesson on the original site.

EDGE, plugins, and the persistent runtime

NativePHP gives you three systems that are worth understanding before you write a single line of code: EDGE for native UI, plugins for device access, and the persistent runtime for performance. This lesson introduces all three at a concept level — each one gets hands-on coverage in its own chapter later in the course.

Why native UI needs more than HTML

Every NativePHP app has a WebView — a browser engine embedded inside the native shell. Your Laravel routes render HTML, that HTML appears in the WebView, and the user interacts with it. For most of your app this is exactly right.

The problem is the chrome around your content. Navigation bars, tab bars, side drawers — these belong to the operating system, not to a web page. When you build them in HTML they live inside the WebView. They get redrawn on every page navigation. On iOS they do not automatically adopt Safe Area insets, Dark Mode, or the platform's design language. On Android they do not match Material You. Users notice, even if they cannot say exactly why.

This is the gap that EDGE closes.

EDGE: Element Definition and Generation Engine

EDGE is a component system built into NativePHP. You define navigation UI using Blade syntax, but it renders using the platform's own native UI framework — entirely outside the WebView.

flowchart LR
    A[Blade template\nnative: components] --> B[PHP compiles\nto JSON config]
    B --> C[Native bridge\npasses config to OS]
    C --> D[iOS or Android\nrenders native UI]
    D --> E[Persists across\nall page navigations]

You write this in your main layout file:

<native:bottom-nav>
    <native:bottom-nav-item id="home" icon="home" label="Home" url="/home" />
    <native:bottom-nav-item id="notes" icon="document" label="Notes" url="/notes" />
    <native:bottom-nav-item id="settings" icon="settings" label="Settings" url="/settings" />
</native:bottom-nav>

PHP compiles those Blade components into a JSON configuration object and passes it to the native shell over the internal bridge. Swift (on iOS) or Kotlin (on Android) receives that configuration and renders the component using the platform's own toolkit — the same APIs Apple and Google use themselves.

Because the component lives outside the WebView lifecycle, it persists across page navigations. There is no flash, no re-render, no moment where the tab bar disappears and reappears. That single fact is what separates an app that feels native from one that almost does.

The three navigation components available are:

  • Bottom Navigation — the tab bar at the bottom of the screen
  • Top Bar — a title bar with optional action buttons
  • Side Navigation — a slide-out navigation drawer

This is just the introduction. EDGE has enough depth — props, theming, per-platform differences, Inertia integration — that it gets a dedicated chapter later. For now, keep the mental model: Blade in, native UI out, WebView not involved.

Plugins: device APIs as Composer packages

NativePHP does not bundle every possible native capability in its core package. Instead, each device API ships as an independent Composer package. You install what you need, register it, and NativePHP compiles the necessary native bridge code into your next build.

composer require nativephp/mobile-biometrics
php artisan native:plugin:register

The native:run command will warn you if your code references a plugin that has not been registered — so you catch the mistake at development time rather than after a lengthy build.

This is the pattern you will follow every time you reach for a native feature throughout this course: camera, push notifications, file access, health data — all arrive via a plugin. The two-step process is always the same.

The persistent runtime: why the app feels instant

NativePHP keeps the PHP runtime alive between requests. Laravel boots once when your app starts, and the kernel is reused across every subsequent user interaction. This is why tapping through a NativePHP app feels immediate — response times run in the range of 5–30ms.

// config/nativephp.php
'runtime' => [
    'mode' => env('NATIVEPHP_RUNTIME_MODE', 'persistent'),
    'reset_instances' => true,
    'gc_between_dispatches' => false,
],

The default mode is persistent. If you ever need to reset to a per-request boot cycle, you can switch to classic. If the persistent boot fails for any reason, NativePHP falls back to classic automatically — no silent crashes.

The persistent runtime also enables a background queue worker running on its own thread. Long-running jobs — syncing data to a remote API, sending notifications, processing files — run without blocking the UI. Set QUEUE_CONNECTION=database and dispatch jobs exactly as you would in any Laravel application.

What this means for the rest of the course

These three systems underpin everything you will build. Plugins give you device access. EDGE gives you native navigation that actually looks like it belongs on the platform. The persistent runtime makes the whole thing feel fast.

You do not need to implement any of this yet. That comes chapter by chapter. What matters now is the mental model: when we build navigation, it will not be HTML in a WebView. It will be native components driven by PHP. That distinction is what makes the apps in this course worth shipping.

Remember

EDGE components render outside the WebView using the OS's own UI toolkit. They look native because they are native — PHP just decides what to show.

From Using Native PHP — Introduction and first screen on mobile