Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

react-native-simple-ota

gupta-shrinath290MIT0.2.1TypeScript support: included

A simple ota solution for React Native apps

react-native, ios, android, ota, self hosted, custom ota, expo

readme

react-native-simple-ota

NPM 0.2.1 Github

A lightweight OTA (Over-The-Air) update solution for React Native apps. It allows you to update your JavaScript code without going through App Store or Play Store releases.


🚀 Concepts

  • OTA (Over-The-Air) Updates: Updates pushed remotely to the app without requiring a new version from the App Store / Play Store.
  • JS Bundle: The compiled version of your React Native JavaScript code. It can generated using the following commands.
    • Android: react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res
    • iOS: react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/main.jsbundle
  • ⚠️ Note:
    • react-native-simple-ota only updates JavaScript code, not native modules.
    • Works only in release builds; in debug mode, Metro bundler is used instead.

🧩 Design Overview

  • react-native-simple-ota stores and loads custom JS bundles (OTA updates) from the local device storage.
  • OTA updates are applied on the next app launch.
  • You control when and how to download OTA updates by implementing a custom OtaUpdateProvider. This ensures full flexibility for caching, version control, and download logic.
  • On app launch, the library simply redirects the JS bundle loader to your downloaded OTA file (if available).
  • react-native-simple-ota works with React Native Old & New Architecture and Expo Bare Work Flow.

Recommendations

  • Host your JS Bundles on to CDNs for faster delivery.
  • Host a compressed version of the JavaScript bundle, as these files can be quite large.
  • Validate hash of JS Bundle after download to avoid corruption.
  • Take a look at the example project for reference.

📦 Installation

npm install react-native-simple-ota

Integration

Step 1: Define implementation of OTAUpdateProvider

import {
  getBundleVersion,
  type OtaUpdate,
  type OtaUpdateProvider,
} from 'react-native-simple-ota';


class MyOtaUpdateProvider implements OtaUpdateProvider {
  isUserApplicableForUpdate(): boolean {
    return true;
  }

  async getOtaUpdate(): Promise<OtaUpdate | null> {
    try {
      const bundleInfo: BundleInfo = await this.getUpdate();
      const update: OtaUpdate = {
        bundleVersion: bundleInfo.bundle_version,
        bundlePath: bundlePath,
      };
      return update;
    } catch (e: any) {
    }
    return null;
  }
}
import { init } from 'react-native-simple-ota';

init(new MyOtaUpdateProvider());

Step 3: Perform OTA update check & Save the OTA JS bundle path

import {
  applyOTAIfApplicable,
} from 'react-native-simple-ota';

// ...

useEffect(() => {
   applyOTAIfApplicable();
}, []);

Step 4: Native Side Integration

Note: Though the below file are in Kotlin and Swift they can be used in Java and Objective C respectively

  • Android The Application class might not exist in your Android code so you'll have to create it at myapp/android/app/src/main/java and add it in manifest file.

import android.app.Application
import dev.droid.simpleota.ReactNativeSimpleOtaModule

class MainApplication : Application(), ReactApplication {

  override val reactNativeHost: ReactNativeHost =
      object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages

        override fun getJSMainModuleName(): String = "index"


        override fun getJSBundleFile(): String? {
          return ReactNativeSimpleOtaModule.getJSBundleFile(this@MainApplication) ?: super.getJSBundleFile()
        }
        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      }
}      
  • iOS `swift

import UIKit import React import React_RCTAppDelegate import ReactAppDependencyProvider import ReactNativeSimpleOta

@main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow?

var reactNativeDelegate: ReactNativeDelegate? var reactNativeFactory: RCTReactNativeFactory?

func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { let delegate = ReactNativeDelegate() let factory = RCTReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider()

reactNativeDelegate = delegate
reactNativeFactory = factory

window = UIWindow(frame: UIScreen.main.bounds)

factory.startReactNative(
  withModuleName: "SimpleOtaExample",
  in: window,
  launchOptions: launchOptions
)

return true

} }

class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { override func sourceURL(for bridge: RCTBridge) -> URL? { self.bundleURL() }

override func bundleURL() -> URL? {

if DEBUG

  return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")

else

if let customBundlePath = ReactNativeSimpleOta.getJSBundleFile() {
      return URL(fileURLWithPath: customBundlePath)
}
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")

endif

} }