in-app-update
v0.1.0 · Android & iOS · TurboModule

Keep every user on the latest version.

A small, typed wrapper around Google Play In-App Updates on Android and an App Store version check on iOS. Add one hook to your root component and users are asked to update whenever a newer version is out.

$ npm install @nameisjayant/react-native-in-app-update
License MIT Android Play Core app-update 2.1.0 iOS StoreKit Web safe no-op

Features

Everything you need to ship updates, nothing you have to configure.

Mandatory by default

Shows the update screen on app open and on every return to the foreground, and again if the user backs out.

Flexible updates

Downloads in the background with progress, so you can show your own “Restart to update” prompt.

One hook

useInAppUpdate() checks, prompts, resumes interrupted updates and installs.

iOS too

Checks the App Store for a newer version and shows the App Store page inside your app. No API keys.

Imperative API

Full control with checkForUpdate, startUpdate, completeUpdate and an install-state listener.

Safe everywhere

Built as a TurboModule for the New Architecture. On web it reports unsupported, so one codebase runs anywhere.

Requirements

RequirementVersion
React NativeNew Architecture (TurboModules) enabled
Android minSdkVersion24+
iOS15.1+ (React Native's minimum)
DistributionAndroid: app must be installed from Google Play. iOS: app must be live on the App Store.

Installation

terminal
npm install @nameisjayant/react-native-in-app-update

On iOS, install pods:

terminal
cd ios && pod install

No extra setup on either platform. Autolinking adds the Play Core dependency on Android and links StoreKit on iOS.

Quick start

Call the hook once in your root component.

App.tsx
import { useInAppUpdate } from '@nameisjayant/react-native-in-app-update';

export default function App() {
  useInAppUpdate({ enabled: !__DEV__ });

  return <RootNavigator />;
}

That's all. When a new version is on Play or the App Store, users see the update screen and can't continue until they update.

Tip: enabled: !__DEV__ turns the hook off in debug builds. Builds installed from Android Studio or yarn android aren't from Play, so they can't receive updates.

How it works

Same hook, native behaviour on each platform.

Android · Google Play

  1. Check Play's In-App Updates API for a newer versionCode.
  2. Prompt with Play's full-screen immediate update (or a flexible background download).
  3. Install and restart. Interrupted updates are resumed automatically.

iOS · App Store

  1. Look up your bundle ID with Apple's iTunes Lookup API (device region, then US).
  2. Compare the store version with CFBundleShortVersionString.
  3. Show your App Store page inside the app (SKStoreProductViewController), falling back to the App Store app.

Status lifecycle

The hook's status moves through these states:

idle→ checking→ prompting→ downloading→ downloaded→ installing

Branches: up_to_date when there's nothing new, canceled when the user dismisses (re-prompted when mandatory), error on failure, unsupported on web.

Usage

Mandatory update (default)

tsx
useInAppUpdate(); // same as useInAppUpdate({ mandatory: true })

How it behaves on Android:

SituationBehaviour
App opened or resumed, update availableShows Play's full-screen immediate update
User backs out of the update screenShows it again right away (or on next resume)
Immediate update was interruptedResumes it
Play doesn't allow an immediate updateDownloads it as a flexible update and installs it automatically when done
Flexible update downloaded in an earlier sessionInstalls it on launch
No update, or not installed from PlayDoes nothing (status is up_to_date or error)

API reference

useInAppUpdate(options?)

Options

OptionTypeDefaultDescription
mandatorybooleantrueForce the update: immediate flow (App Store page on iOS), shown again on cancel, flexible downloads installed automatically
type'immediate' | 'flexible''flexible'Android flow when mandatory is false (ignored on iOS)
checkOnMountbooleantrueCheck when the hook mounts (app open)
checkOnResumebooleantrueCheck again whenever the app returns to the foreground
enabledbooleantrueTurn the hook off entirely (e.g. !__DEV__)
onError(error: Error) => void—Called with any Play or App Store error

Returns

FieldTypeDescription
statusUpdateStatusCurrent state of the update flow
updateInfoUpdateInfo | nullResult of the last check
errorError | nullLast error, if any
progressnumberFlexible download progress, 0–1
checkNow() => Promise<void>Run the check → update flow right now
completeUpdate() => Promise<void>Install a downloaded flexible update (restarts the app)

UpdateStatus

ValueMeaning
idleNothing has happened yet
checkingAsking Play or the App Store for updates
up_to_dateNo update available
promptingThe Play update screen or App Store page is showing
canceledUser dismissed the update screen (shown again when mandatory)
downloadingFlexible update is downloading
downloadedFlexible update is ready; call completeUpdate()
installingUpdate is being installed
unsupportedPlatform has no in-app updates (web)
errorSomething failed (see error)

Functions

FunctionReturnsDescription
checkForUpdate()Promise<UpdateInfo>Asks Google Play or the App Store whether a newer version is available
startUpdate(type = 'immediate')Promise<UpdateFlowResult>Shows the Play update UI, or the App Store page on iOS. Rejects if no update can be started (and always on web)
completeUpdate()Promise<void>Installs a downloaded flexible update. The app restarts. No-op on iOS
addInstallStateListener(listener)EventSubscriptionListens to download/install progress. Call .remove() to unsubscribe. Never fires on iOS

Types

types.ts
type UpdateType = 'immediate' | 'flexible';

type UpdateFlowResult = 'accepted' | 'canceled' | 'failed'; // always 'canceled' on iOS

type UpdateInfo = {
  updateAvailability: 'unknown' | 'not_available' | 'available' | 'in_progress' | 'unsupported';
  installStatus: InstallStatus;
  availableVersionCode: number;              // Play versionCode, 0 on iOS
  availableVersion: string | null;           // App Store version, null on Android
  releaseNotes: string | null;               // App Store "What's New", null on Android
  isImmediateAllowed: boolean;
  isFlexibleAllowed: boolean;
  clientVersionStalenessDays: number | null; // days since Play learned about / App Store released it
  updatePriority: number;                    // 0–5, set via the Play Developer API, 0 on iOS
  bytesDownloaded: number;
  totalBytesToDownload: number;
};

type InstallState = {
  installStatus: InstallStatus;
  installErrorCode: number; // Play InstallErrorCode, 0 = no error
  bytesDownloaded: number;
  totalBytesToDownload: number;
};

type InstallStatus =
  | 'unknown' | 'pending' | 'downloading' | 'downloaded'
  | 'installing' | 'installed' | 'failed' | 'canceled' | 'requires_ui_intent';

All types are exported from the package:

ts
import type { UpdateInfo, UpdateStatus, UseInAppUpdateOptions } from '@nameisjayant/react-native-in-app-update';

Platform support

PlatformBehaviour
AndroidFull support via Google Play In-App Updates
iOSApp Store version check + in-app App Store page
WebNo-op. checkForUpdate() returns updateAvailability: 'unsupported'; the hook reports status: 'unsupported'

startUpdate() throws on web. The hook never does, so it's safe to call unconditionally.

Testing in-app updates

In-app updates only work for an app installed from Google Play. Use internal app sharing to test without publishing.

  1. Set up. In Play Console, enable internal app sharing and add your Google account as a tester. On the device, open Play Store → Settings → About, tap Play Store version 7 times, then turn on Internal app sharing.
  2. Install v1. Build a release APK/AAB with versionCode 1, upload it to internal app sharing, open the link on the device and install it.
  3. Publish v2. Bump to versionCode 2, build and upload again. Open the new link but don't tap Update. Just let Play see it.
  4. Try it. Open the app from step 2. The Play update screen appears; press back and it appears again.

The example/ app shows the current status and version info, with a Mandatory / Flexible switch.

Troubleshooting

ProblemCause / fix
Install Error(-10): The app is not owned by any user on this deviceThe app wasn't installed from Play (e.g. yarn android). Expected in dev. The hook reports status: 'error' and the app keeps working. Use enabled: !__DEV__.
Update screen never showsMake sure the installed versionCode is lower than the one on Play, and open the Play Store listing once so Play's cache refreshes.
status is unsupportedYou're on web or another platform without in-app updates.
iOS: up_to_date though a new version is liveApple's lookup can lag a few hours. Also check the bundle ID matches the App Store app and the device meets the new version's minimum iOS.
iOS: E_NOT_AVAILABLE from startUpdate()No newer App Store version for this device. Call checkForUpdate() first.

Development

CommandWhat it does
yarnInstall dependencies (Yarn 4, Node version from .nvmrc)
yarn example android / iosRun the example app
yarn testJS unit tests (Jest): API and hook on Android, iOS and web
yarn test:androidAndroid unit tests (JUnit): Play Core → JS mapping. Needs JDK 21
yarn test:iosiOS unit tests (XCTest): App Store lookup, version compare, update info
yarn typecheckTypeScript check
yarn lintESLint + Prettier
yarn prepareBuild lib/ with react-native-builder-bob

Project layout

tree
src/
  index.tsx              Public exports
  InAppUpdate.native.ts  Imperative API (Android/iOS)
  InAppUpdate.ts         Web fallback
  useInAppUpdate.ts      Mandatory/flexible update hook
  NativeInAppUpdate.ts   TurboModule spec
  types.ts               Public types
android/                 Kotlin module, Google Play Core app-update
ios/                     Objective-C++ module, App Store lookup + SKStoreProductViewController
example/                 Example app using the library from source