NativePHP v3: Building Your First Laravel Mobile App
You already know Laravel. You can write controllers, Eloquent queries, Livewire components, and Artisan commands in your sleep. Now you want to ship an iOS or Android app — without learning Swift, Kotlin, React Native, or Dart.
NativePHP v3 makes that possible. This post walks through the exact steps to go from a fresh Laravel 12 project to a running mobile app on a real device, covering the v3 plugin architecture, the persistent PHP runtime, and the mistakes that will waste hours of your time if you skip over them.
For a broader overview of the framework's capabilities, see NativePHP: Building iOS and Android Apps with Laravel.
Prerequisites and Version Requirements
Before running a single Artisan command, confirm your environment:
| Requirement | Minimum version |
|---|---|
| PHP | ^8.3 |
| Laravel | ^10.0 (^12.0 recommended) |
| nativephp/mobile | 3.3.6 (current stable, 2026-06-05) |
| macOS (for iOS builds) | Xcode 15+ required |
| Android Studio | Latest stable, Gradle 8+ |
| Node.js | 20+ (for asset compilation) |
WSL is not supported. If you develop on Windows, install PHP and Composer natively. Exclude C:\temp and your project folder from Windows Defender scanning — it significantly slows Gradle builds.
iOS builds require your physical device to be in Developer Mode and registered inside your Apple Developer account. Android requires Developer Options and USB Debugging enabled.
Step 1: Install the Package
composer require nativephp/mobile
php artisan native:install
During native:install, the CLI prompts you to choose between ICU and non-ICU PHP binaries. Choose ICU if you plan to use Filament or any package that depends on the PHP intl extension. You cannot switch later without a full reinstall.
The install command creates a nativephp/ directory at your project root. Add it to .gitignore immediately. It is an ephemeral build artifact that is fully regenerated every time you run native:install. Committing it causes merge conflicts and bloats your repository with binary files.
echo "nativephp/" >> .gitignore
Step 2: Understand the Core Configuration
The install command publishes config/nativephp.php. This single file controls almost everything:
// config/nativephp.php
return [
'app_id' => env('NATIVEPHP_APP_ID', 'com.example.myapp'),
'app_version' => env('NATIVEPHP_APP_VERSION', 'DEBUG'),
'runtime' => [
'mode' => 'persistent', // 'persistent' (v3.1+) or 'classic'
'reset_instances' => true,
'gc_between_dispatches' => false,
],
'start_url' => '/',
'deeplink_scheme' => 'myapp',
'ios' => [
'development_team' => env('NATIVEPHP_DEVELOPMENT_TEAM'),
'ipad' => false,
],
'android' => [
'compile_sdk' => env('NATIVEPHP_ANDROID_COMPILE_SDK', 36),
'min_sdk' => env('NATIVEPHP_ANDROID_MIN_SDK', 33),
'target_sdk' => env('NATIVEPHP_ANDROID_TARGET_SDK', 36),
'minify_enabled' => true,
'shrink_resources' => true,
'obfuscate' => false,
],
'cleanup_env_keys' => [
'APP_KEY', 'DB_PASSWORD',
],
];
Two things to set immediately:
-
app_id— must be a valid reverse-domain identifier (com.yourcompany.appname). Changing it later invalidates all existing installations. -
cleanup_env_keys— strip every secret before packaging. At minimum includeAPP_KEYandDB_PASSWORD. Any key listed here is removed from the.envthat gets bundled into the binary.
Note the runtime.mode => 'persistent' default. This is the v3.1 persistent PHP runtime, which keeps Laravel booted between requests. Response times drop from 200–300ms (classic mode) to 5–30ms. Leave it on unless you encounter stale singleton state issues.
Step 3: Register the NativeServiceProvider
v3 introduced a mandatory security gate. Every third-party NativePHP plugin must be explicitly registered in NativeServiceProvider before its native Swift/Kotlin code is compiled into your binary. Forgetting this is the most common reason a plugin installs via Composer but silently does nothing at runtime.
// app/Providers/NativeServiceProvider.php
namespace App\Providers;
use Native\Mobile\Facades\NativeApp;
use Illuminate\Support\ServiceProvider;
class NativeServiceProvider extends ServiceProvider
{
public function boot(): void
{
NativeApp::allPlugins([
// Register third-party plugins explicitly:
// \Vendor\SomePlugin\Plugin::class,
]);
}
}
The built-in plugins (Camera, Biometrics, SecureStorage, Geolocation, etc.) are part of the core package and do not require manual registration here. Third-party plugins from external Composer packages do.
Step 4: Run on a Device
# Development — hot reload via Jump (added in v3.3.0)
php artisan native:run
# Test a release build locally before submitting
php artisan native:run --build=release
native:run compiles the PHP runtime and native shell, deploys to the connected device, and opens a WebSocket bridge for Jump — NativePHP's live preview feature introduced in v3.3.0. Changes to Blade, Livewire, or Vue files reflect on the device without a full rebuild.
For iOS, specify your team ID if it is not already in your .env:
NATIVEPHP_DEVELOPMENT_TEAM=ABC1234567 php artisan native:run
Step 5: Use Native Device APIs
All native capabilities are exposed as PHP facades. Here are two you will use in almost every app.
SecureStorage (iOS Keychain / Android Keystore)
use Native\Mobile\Facades\SecureStorage;
// Write a token after login
SecureStorage::set('api_token', $response['token']); // returns bool
// Read it on subsequent requests
$token = SecureStorage::get('api_token'); // returns string|null
// Wipe on logout
SecureStorage::delete('api_token'); // returns bool
SecureStorage uses hardware-backed encryption where available. On Android, data is deleted when the app is uninstalled. On iOS with iCloud Keychain enabled, the data persists through reinstalls — useful for remember-me flows.
Do not use SecureStorage as a general-purpose database. It is designed for small credentials — tokens, keys, PINs. For structured data, use SQLite via Eloquent.
Biometrics (Face ID / Fingerprint)
use Native\Mobile\Facades\Biometrics;
use Native\Mobile\Attributes\OnNative;
use Native\Mobile\Events\Biometric\Completed;
// In a controller or Livewire component method:
Biometrics::prompt();
// Handle the async result:
#[OnNative(Completed::class)]
public function handleBiometric(bool $success): void
{
if ($success) {
$secret = SecureStorage::get('vault_key');
$this->unlockVault($secret);
}
}
Biometric prompts are asynchronous. The #[OnNative] attribute wires the event handler to the native callback — no polling, no JavaScript bridges you have to manage manually.
Step 6: Package for the App Stores
Version bump
php artisan native:release patch # 1.0.0 → 1.0.1
php artisan native:release minor # 1.0.0 → 1.1.0
php artisan native:release major # 1.0.0 → 2.0.0
This bumps NATIVEPHP_APP_VERSION in your .env and increments the build number. Always run this before packaging.
iOS (App Store)
php artisan native:package ios \
--export-method=app-store \
--api-key-path=/path/to/AuthKey.p8 \
--api-key-id=ABC123DEF \
--api-issuer-id=01234567-89ab-cdef-0123-456789abcdef \
--certificate-path=/path/to/distribution.p12 \
--certificate-password=secret \
--provisioning-profile-path=/path/to/profile.mobileprovision \
--team-id=ABC1234567 \
--upload-to-app-store
Android (Google Play — AAB format)
# Generate keystore and write credentials to .env (run once)
php artisan native:credentials android
# Build and upload AAB
php artisan native:package android \
--build-type=bundle \
--keystore=/path/to/my-app.keystore \
--keystore-password=pass \
--key-alias=my-app-key \
--key-password=pass \
--upload-to-play-store \
--play-store-track=internal \
--google-service-key=/path/to/service-account-key.json
native:credentials android auto-adds the .keystore file to .gitignore. Do not commit keystore files or Apple .p8 keys to version control under any circumstances.
Upgrading NativePHP (The Step Everyone Skips)
After every composer update that changes the nativephp/mobile version, run:
php artisan native:install --force
The --force flag regenerates the entire nativephp/ directory from the new package version. Skipping this leaves old native project files that conflict with the new PHP bindings. Symptoms include cryptic Gradle errors, Xcode linking failures, or plugins that stop responding at runtime.
If you upgraded from v3.3.3 or earlier and your app targets PHP 8.3, upgrade to at least 3.3.4 — PHP 8.3 support was temporarily broken in v3.3.x and was restored in that patch.
Common Mistakes and Limitations
Committing nativephp/ — it is a build artifact. Gitignore it.
Storing shared secrets in APP_KEY-encrypted fields — since v3, each device generates its own APP_KEY at first run, stored in the device Keystore/Keychain. Data encrypted on one device is unrecoverable on another. Never sync Crypt::-encrypted data across devices.
Embedding API keys in the binary — Android APKs are zip archives. Any string in your binary is extractable. Use Play Integrity API (Android) or App Attest (iOS) to authenticate your app to a backend without embedding a shared secret.
Missing android config keys on v3.1+ — if you upgraded from v3.0, add compile_sdk, min_sdk, and target_sdk to the android array in config/nativephp.php or builds will fail.
Choosing non-ICU binaries then using Filament — the intl PHP extension is only available in ICU builds. Reinstall with ICU binaries if you hit this.
Omitting --validate-profile on iOS — mismatched provisioning profiles and entitlements are the most common cause of App Store submission rejection.
Tradeoffs Worth Knowing
NativePHP ships a full PHP runtime inside the binary, which increases app size compared to React Native or Capacitor. The exact impact is not benchmarked in official documentation.
The persistent runtime (v3.1+) yields 5–30ms response times, but Laravel boots once and singletons are shared across requests within a session. If a service caches stale state, you may see subtle bugs that do not appear in a standard HTTP context. The reset_instances and gc_between_dispatches config keys exist to mitigate this.
NativePHP mobile shipped its first stable version in May 2025. v3 arrived in early 2026. The framework is under two years old. React Native (2015) and Capacitor (2019) have considerably larger ecosystems and more production tooling. NativePHP's plugin catalog is still growing — 30+ repositories in the GitHub org as of June 2026.
NativePHP v3 is the most practical path for a Laravel team that needs a mobile app without hiring native developers or rewriting in a new language. The install is straightforward, the Artisan workflow is familiar, and the persistent runtime makes it genuinely fast enough for production use.
If you need Laravel development in Mumbai, Mumbai Web Designer builds production-grade Laravel applications.
Top comments (0)