Force Update Plugin for NativePHP Mobile#
Cross-platform minimum-version gate that opens the correct App Store or Play listing.
Overview#
You set a minimum (and optional latest) version in PHP. The plugin reads the running app version from NativePHP — config('nativephp.version'), which is NATIVEPHP_APP_VERSION — and reports whether the installed build is below that minimum. openStore() launches the iOS App Store URL or Android Play listing URL you configured.
This is the product buyers actually need: "this installed build is too old — block or warn, and send them to the store."
It is not a clone of wilsonatb/in-app-update. That plugin wraps Google Play Core flexible/immediate flows on Android only; iOS returns unsupported. This plugin is iOS and Android, PHP-side semver, and a store listing open. It does not show Google's in-app update bottom sheet. Play Core is a different plugin.
mode (hard or soft) is a status flag. hard means the app should treat the build as blocking. The plugin does not hijack navigation — your app reads status() and shows its own screen.
Installation#
composer require partek/force-update
Don't forget to register the plugin:
php artisan native:plugin:register partek/force-update
If you have not published NativePHP's plugin provider yet:
php artisan vendor:publish --tag=nativephp-plugins-provider
Rebuild after native or manifest changes:
php artisan native:run
Usage#
Configure once at boot#
Call configure() once from NativeServiceProvider::boot or AppServiceProvider::boot.
Do not put boot-time configure() (or a set()-style call) in a Livewire component or NativeComponent mount() / render(). Shane rejected that pattern for Home Screen Shortcuts; the same rule applies here. Screens read status. Providers configure.
use PARTek\ForceUpdate\Facades\ForceUpdate; public function boot(): void{ ForceUpdate::configure([ 'minimum' => '1.2.0', 'latest' => '1.4.0', 'ios_url' => 'https://apps.apple.com/app/id123456789', 'android_url' => 'https://play.google.com/store/apps/details?id=com.example.app', 'mode' => 'hard', ]);}
PHP (Super Native / Livewire / Blade)#
use PARTek\ForceUpdate\Facades\ForceUpdate; $status = ForceUpdate::status();// [// 'current' => '1.1.0',// 'minimum' => '1.2.0',// 'latest' => '1.4.0',// 'outdated' => true,// 'storeUrl' => 'https://apps.apple.com/app/id123456789',// 'mode' => 'hard',// ] if (ForceUpdate::isOutdated()) { // Render your own blocking or warning screen. // The plugin will not redirect for you.} ForceUpdate::openStore(); // bool — false if this platform has no URL
outdated is current < minimum. latest is informational only. A DEBUG NATIVEPHP_APP_VERSION (NativePHP's development default) is never outdated.
JavaScript (Vue / React / Inertia)#
configure(), status(), and isOutdated() are PHP-only. Compute them on the server and pass storeUrl into the page. JS wraps the native store open:
import { openStore, ForceUpdate } from '../../vendor/partek/force-update/resources/js/index.js'; await openStore({ url: storeUrl });// orawait ForceUpdate.openStore({ url: storeUrl });
The JS client POSTs to /_native/api/call with an X-CSRF-TOKEN header and throws on HTTP or native errors.
Events#
None.
An UpdateRequired event on every status() call would fire on every request that reads the gate. configure() runs at boot and must not imply "outdated" until the app asks. Skipping events is intentional.
Methods#
configure(array $options): void#
| Key | Type | Required | Description |
|---|---|---|---|
minimum |
string | yes | Semver minimum (e.g. 1.2.0). Installed builds below this are outdated. |
latest |
string | no | Optional newest published version. Informational only. |
ios_url |
string | no | App Store listing URL (https, itms-apps, or itms). |
android_url |
string | no | Play listing URL (https or market). |
mode |
hard | soft |
no | Status flag. Default hard. hard means the app should block; the plugin still does not intercept routes. |
Throws if minimum is missing or not semver, if latest / URLs / mode are invalid, or if unknown keys are passed.
status(): array#
| Key | Type | Description |
|---|---|---|
current |
string | Running app version from config('nativephp.version'). |
minimum |
string | Configured minimum. |
latest |
string | null | Configured latest, if any. |
outdated |
bool | true when current < minimum. |
storeUrl |
string | null | Listing URL for this platform, or null if missing / unknown platform. |
mode |
hard | soft |
Configured mode. |
Throws if configure() has not been called.
isOutdated(): bool#
true when the running version is a valid semver and is less than minimum. DEBUG and unreadable versions return false.
openStore(): bool#
Opens the platform store URL via native (UIApplication.open on iOS, Intent.ACTION_VIEW on Android).
Returns false and does not call native when:
- the URL for this platform is missing
- the platform cannot be detected (off-device / tests without a platform)
- the native bridge is unavailable or reports an error
Fail closed. Document the missing URL in your app UI (status()['storeUrl'] is null).
Platform behavior#
| Platform | Version source | Store open |
|---|---|---|
| iOS | config('nativephp.version') |
UIApplication.shared.open |
| Android | config('nativephp.version') |
Intent.ACTION_VIEW |
| Off-device | same config / NATIVEPHP_APP_VERSION |
openStore() returns false |
- Android min SDK 26. iOS min 18.0 in the manifest (the open-URL APIs themselves work on iOS 14+).
- No extra permissions. No Play Core dependency.
- Version is read in PHP. There is no
ForceUpdate.AppVersionbridge.
Testing#
The plugin ships Pest tests for semver compare (1.0.19 vs 1.0.18, 2.0.0 vs 1.9.9), configure validation, and missing-URL fail-closed behavior.
composer test
App tests can construct status without native:
use PARTek\ForceUpdate\Facades\ForceUpdate; config()->set('nativephp.version', '1.0.18'); ForceUpdate::configure([ 'minimum' => '1.0.19', 'ios_url' => 'https://apps.apple.com/app/id123456789',]); expect(ForceUpdate::isOutdated())->toBeTrue();
openStore() only hits the native bridge when a platform URL exists. Off-device it returns false.
License#
Proprietary commercial. See LICENSE.