Documentation

Install and connect native

Follow the path for React Native, Expo or HarmonyOS and prepare a Release build that can load updates.

Updated 2026-09-15

On this page

Prepare your environment

  • A React Native, Expo or RNOH project that already builds. Commands run from the app root containing package.json unless stated otherwise.

  • Node.js ≥18.17 and your existing platform toolchain; iOS builds require macOS.

  • A separate test application and device. Start with one platform.

SDK peer dependencies declare React ≥16.8 and RN ≥0.59; that is not a test guarantee for every version and architecture. Keep your project's version and follow its host structure below.

Install and obtain an appKey

Choose your project type. Run its commands in the directory containing package.json.

React Native
npm install -g rn-update-cli
npm install rn-update

For iOS, run pod install inside ios (or bundle exec pod install with Bundler), then return to the app root. Expo generates its native project during the build step.

Then sign in and select an application:

bash
pakta login
pakta createApp --platform android --name PaktaDemo
pakta selectApp --platform android

Skip createApp if the test application exists. For iOS / HarmonyOS use ios / harmony. Selection creates or updates update.json in the app root.

Which identifier goes where?

IdentifierSourceUsed by
appKeyApplication details or update.jsonSDK new Pakta({ appKey })
appIdApplication details or update.jsonCLI / management API selection
PAKTA_API_TOKENConsole API token entryCLI / CI only, never the app

Follow only the section matching your project.

Android: connect bundle loading

If MainApplication.kt uses reactHost and getDefaultReactHost, add jsBundleFilePath to the existing host:

MainApplication.kt · ReactHost
import cn.reactnative.modules.update.UpdateContext

// Keep existing ReactHost, PackageList and getDefaultReactHost imports.
override val reactHost: ReactHost by lazy {
  getDefaultReactHost(
    context = applicationContext,
    packageList = PackageList(this).packages,
    jsBundleFilePath = UpdateContext.getBundleUrl(this),
  )
}

For DefaultReactNativeHost / ReactNativeHost, import UpdateContext and override the method in the existing host object:

MainApplication.kt · ReactNativeHost
override fun getJSBundleFile(): String? =
  UpdateContext.getBundleUrl(this@MainApplication)

Disable PNG crunching in the existing release configuration to reduce resource byte differences:

android/app/build.gradle
android {
  buildTypes {
    release {
      crunchPngs false
    }
  }
}

Autolinking connects the module and the SDK's Gradle integration provides build identity. Do not invent buildTime. Retain existing signing and build settings.

Connect the root component, then run ./gradlew assembleRelease from android, or .\gradlew.bat assembleRelease on Windows. Use the matching Release task for flavored projects.

Java projects

Open android/app/src/main/java/your/package/MainApplication.java. Add import cn.reactnative.modules.update.UpdateContext; at the top and this override inside the existing ReactNativeHost or DefaultReactNativeHost object:

MainApplication.java · inside the existing ReactNativeHost
@Override
protected String getJSBundleFile() {
    return UpdateContext.getBundleUrl(MainApplication.this);
}

iOS: use Pakta for Release bundles

Install Pods from ios: bundle exec pod install for Bundler projects, otherwise pod install.

AppDelegate.mm
#import "RCTPakta.h"

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [RCTPakta bundleURL];
#endif
}

Keep existing Debug entry points, module names and lifecycle methods. In Swift templates, the actual Release bundle URL provider must return RCTPakta.bundleURL(). Expose RCTPakta.h through the project's Objective-C bridging header instead of replacing the entire AppDelegate.

After connecting the root, open .xcworkspace in Xcode, select signing and a device, and verify a Release build. Retain the IPA from the same archive for registration.

Swift and older AppDelegate templates

Add #import "RCTPakta.h" to your app target's Objective-C bridging header. If needed, create that header and set its relative path, such as YourApp/YourApp-Bridging-Header.h, in Build Settings → Objective-C Bridging Header.

Modify the existing URL provider. Newer templates may put this method inside ReactNativeDelegate:

AppDelegate.swift · bundleURL
override func bundleURL() -> URL? {
#if DEBUG
  return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
  return RCTPakta.bundleURL()
#endif
}

For an older Objective-C template with only sourceURLForBridge:, retain that signature and return [RCTPakta bundleURL] in its non-DEBUG branch. Mixed native apps should obtain the URL through a bridge delegate rather than create the root view using a fixed bundle path.

Expo: build with native modules

Expo Go does not contain rn-update's native module. Follow these steps:

  1. Install dependencies with npx expo install rn-update. For a custom channel, add the built-in Config Plugin to expo.plugins.

  2. Connect the root layout: wrap the existing Stack / Slot in app/_layout.tsx with UpdateProvider; create the client outside components.

  3. Build the native app: run npx expo run:android --variant release, npx expo run:ios --configuration Release, or your existing EAS production profile.

  4. Install and verify it starts without Metro. Later, create OTA packages using bundle --expo with platform android / ios.

See the Expo guide for the complete steps.

HarmonyOS: wire the RNOH integration

HarmonyOS requires HAR, ArkTS and C++ integration beyond npm installation. Compare these locations with the example:

LocationConnection
harmony/entry/oh-package.json5Pakta HAR and RNOH dependencies, using your paths
harmony/entry/hvigorfile.tsImport SDK harmony/hvigor-plugin; add reactNativeUpdatePlugin() to plugins
RNPackagesFactory.etsImport PaktaPackage and add it to the RNPackage list
PackageProvider.cpp / CMakeRegister the Pakta C++ package and native linkage
pages/Index.etsAdd PaktaFileJSBundleProvider, retaining embedded fallback
App root
pakta bundle --platform harmony --output .pakta/output/harmony.ppk --no-interactive

Sync, sign and build the native .app in DevEco Studio. Confirm generated rawfile paths match the host, install and verify, then register with uploadApp.

Complete projects are in the SDK examples: testHotUpdate, expoUsePakta and harmony_use_pakta. Replace repository-relative paths with your dependency paths.

HarmonyOS file changes

These paths assume harmony and node_modules are siblings in the app root. Merge entries into existing dependencies, plugins and package lists. Retain all existing registrations. Add the HAR, Hvigor plugin, ArkTS and C++ registrations, then set the Release bundle provider:

harmony/entry/oh-package.json5 · dependencies
"pakta": "file:../../node_modules/rn-update/harmony/pakta.har"
harmony/entry/hvigorfile.ts
import { hapTasks } from '@ohos/hvigor-ohos-plugin';
import { reactNativeUpdatePlugin } from '../../node_modules/rn-update/harmony/hvigor-plugin';

export default {
  system: hapTasks,
  plugins: [reactNativeUpdatePlugin()],
};
harmony/entry/src/main/ets/RNPackagesFactory.ets
import type { RNPackageContext, RNPackage } from '@rnoh/react-native-openharmony';
import PaktaPackage from 'pakta';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [new PaktaPackage(ctx)];
}
harmony/entry/src/main/cpp/CMakeLists.txt · after add_library(rnoh_app ...)
set(PAKTA_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules/rn-update/harmony/pakta/src/main/cpp")
target_include_directories(rnoh_app PRIVATE "${PAKTA_CPP_DIR}")
target_sources(rnoh_app PRIVATE "${PAKTA_CPP_DIR}/PaktaTurboModule.cpp")
harmony/entry/src/main/cpp/PackageProvider.cpp
#include "RNOH/PackageProvider.h"
#include "PaktaPackage.h"
using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
  return {std::make_shared<PaktaPackage>(ctx)};
}
harmony/entry/src/main/ets/pages/Index.ets · imports
import { PaktaFileJSBundleProvider } from 'pakta';
import { AnyJSBundleProvider, ResourceJSBundleProvider } from '@rnoh/react-native-openharmony';
Index.ets · RNApp jsBundleProvider
jsBundleProvider: new AnyJSBundleProvider([
  new PaktaFileJSBundleProvider(this.rnohCoreContext.uiAbilityContext),
  new ResourceJSBundleProvider(
    this.rnohCoreContext.uiAbilityContext.resourceManager,
    'bundle.harmony.js'
  ),
]),

Keep the other RNApp properties and ensure rnohCoreContext is ready. Release builds should try the Pakta file before the embedded resource, with no Metro provider ahead of it.

See the complete Expo guide for its root layout and channel plugin.

Self-hosting: configure SDK and CLI separately

Hosted-service users can skip this section. The SDK uses the public update API; the CLI uses management endpoints:

Add to your existing Pakta options
server: {
  main: ['https://YOUR_HOST/api'],
  queryUrls: ['https://YOUR_CDN/endpoints.json'],
},

Replace hosts with your deployment addresses. The discovery file must belong to your deployment. Set the CLI host before signing in:

PowerShell
$env:RNU_SERVICE_URL = 'https://YOUR_HOST'
Bash / zsh
export RNU_SERVICE_URL=https://YOUR_HOST

React Native 0.60+ normally autolinks. Run npx react-native config to check rn-update discovery and install iOS Pods. Skip manual registration if already linked.

For older Android projects add the library in settings.gradle, reference it in app dependencies, and add new UpdatePackage() to the existing getPackages list, importing cn.reactnative.modules.update.UpdatePackage. Configure all three:

android/settings.gradle
include ':rn-update'
project(':rn-update').projectDir = new File(rootProject.projectDir, '../node_modules/rn-update/android')
android/app/build.gradle · dependencies
implementation project(':rn-update')

If iOS discovery fails, add pod 'rn-update', :path => '../node_modules/rn-update' inside the app target in Podfile and run pod install. Linking alone does not configure the bundle loading entry shown above.

Android activity restoration with react-native-screens

For apps using react-native-screens, check MainActivity restoration configuration. The following requires a version that supplies RNScreensFragmentFactory. Place the override directly in MainActivity, not MainActivityDelegate:

MainActivity.kt · imports and onCreate
import android.os.Bundle
import com.swmansion.rnscreens.fragment.restoration.RNScreensFragmentFactory

override fun onCreate(savedInstanceState: Bundle?) {
  supportFragmentManager.fragmentFactory = RNScreensFragmentFactory()
  super.onCreate(savedInstanceState)
}

Merge into an existing onCreate instead of adding a duplicate. Older releases without this class need their own screens installation instructions.

An existing scanner can call parseTestQrCode. To open the app from a browser or camera, configure your own scheme such as paktademo.

Add a separate filter inside the existing Android MainActivity activity, keep its launcher filter, and use android:launchMode="singleTask":

AndroidManifest.xml · MainActivity
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="paktademo" />
</intent-filter>

For iOS add paktademo in the app target's Info → URL Types and retain the app's React Native Linking forwarding in AppDelegate. Expo uses expo.scheme. Rebuild the native installer.

After uploading a test update, copy its full hash and open paktademo://update?type=__rnPaktaVersionHash&data=ACTUAL_HASH. The Provider checks it and follows your app's download/activation flow. testChannel:false rejects test payloads.

Android AAB installers

Retain the AAB actually submitted to Google Play. Inspect with pakta parseAab PATH, then register with pakta uploadAab PATH. Do not substitute an APK from a different build.

Do not disable every language, density and ABI split just to integrate. Verify resources and updates using the distributed build. Investigate resource packaging and the native version when a split lacks an asset.

Next step

Dependencies are installed, the CLI has selected the correct platform application, and native loading is configured. Connect your app, add the version panel, then build the native package.

Local Expo build flags follow the official Expo CLI reference.