# Install and connect native

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

## Prepare your environment {#prerequisites}

- 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 the SDK {#install}

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

```bash group="Install" tab="React Native" title="React Native"
npm install -g rn-update-cli
npm install rn-update
```

```bash group="Install" tab="Expo" title="Expo"
npm install -g rn-update-cli
npx expo install rn-update
```

For React Native iOS projects, 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](/docs/expo#build); run `npx expo prebuild` first when the native directories do not exist.

> [!WARNING] Do not mix npm, yarn or pnpm, or commit multiple lockfiles. Pick one package manager for the team and keep its lockfile.

> [!NOTE] Changes under `ios` or `android` require a new native build. A Metro-connected development app does not replace Release verification.

## Sign in and create or select an application {#app-config}

Register in the Pakta console, then sign in from the app root:

```bash title="App root"
pakta login
```

By default, the CLI stores the session as `.pakta.token` in the current directory and adds the CLI temporary directory `.pakta` to `.gitignore` when the project already has one. Confirm neither path is committed; never put passwords, session files or `PAKTA_API_TOKEN` in the repository. When `RNU_SERVICE_URL` points to a self-hosted service, credentials are stored separately per service URL.

### Create an application

Android, iOS and HarmonyOS are separate platform applications. Create each one as needed. The CLI selects a newly created application and writes it to `update.json`:

Run only the lines for platforms you actually ship; do not create unused platform applications.

```bash
pakta createApp --platform android --name PaktaDemo
pakta createApp --platform ios --name PaktaDemo
pakta createApp --platform harmony --name PaktaDemo
```

The names may match; the platform must match the installer you build.

### Select an existing application

If the application already exists in the console, list the platform's applications and select one:

```bash
pakta apps --platform android
pakta selectApp --platform android
```

The CLI shows the application and its `appId`; enter the `appId` you want to use. Replace the platform with `ios` or `harmony` for those targets. A single `update.json` can hold all three selections; selecting one platform preserves the other entries.

### `appId`, `appKey` and `update.json` {#appkey}

After selection, the app root contains a file like this:

```json title="update.json"
{
  "android": {
    "appId": "YOUR_ANDROID_APP_ID",
    "appKey": "YOUR_ANDROID_APP_KEY"
  },
  "ios": {
    "appId": "YOUR_IOS_APP_ID",
    "appKey": "YOUR_IOS_APP_KEY"
  },
  "harmony": {
    "appId": "YOUR_HARMONY_APP_ID",
    "appKey": "YOUR_HARMONY_APP_KEY"
  }
}
```

The real file contains only platforms you have created or selected. Legacy services may write numeric `appId` values; the Pakta standalone service may write UUIDs. Both are server-generated application identities. Do not edit, sort or convert them.

| Identifier | Sensitive? | Used for |
| --- | --- | --- |
| `appId` | No | CLI, management API and native package registration |
| `appKey` | Public identifier | `new Pakta({ appKey })` in the app |
| `.pakta.token` | Yes | Local CLI session; keep it in `.gitignore` |
| `PAKTA_API_TOKEN` | Yes | CI / CLI publishing; inject through Secrets only |

`update.json` contains no publishing credential and can be committed for team sharing. If application code imports it, its contents enter the JavaScript bundle; when only the CLI uses it, it remains publishing configuration. See [root component setup](/docs/integration#platform-key) for platform-specific `appKey` loading.

Follow only the section matching your project.

## Android: connect bundle loading {#android}

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

```kotlin title="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:

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

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

```groovy title="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](/docs/integration), then run `./gradlew assembleRelease` from `android`, or `.\gradlew.bat assembleRelease` on Windows. Use the matching Release task for flavored projects.

### Java projects {#android-java}

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:

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

## iOS: use Pakta for Release bundles {#ios}

Install Pods from `ios`: `bundle exec pod install` for Bundler projects, otherwise `pod install`. Choose one matching your RN template below: RN 0.74+ uses `bundleURL`; older templates usually use `sourceURLForBridge:`.

```objc group="iOS bundle URL" tab="RN 0.74+" title="AppDelegate.mm · bundleURL"
#import "RCTPakta.h"

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

```objc group="iOS bundle URL" tab="RN 0.73 and older" title="AppDelegate.mm · sourceURLForBridge"
#import "RCTPakta.h"

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#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](/docs/integration), 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 {#ios-swift}

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`:

```swift title="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}

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](/docs/expo) for the complete steps.

## HarmonyOS: wire the RNOH integration {#harmonyos}

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

| Location | Connection |
| --- | --- |
| `harmony/entry/oh-package.json5` | Pakta HAR and RNOH dependencies, using your paths |
| `harmony/entry/hvigorfile.ts` | Import SDK `harmony/hvigor-plugin`; add `reactNativeUpdatePlugin()` to plugins |
| `RNPackagesFactory.ets` | Import `PaktaPackage` and add it to the RNPackage list |
| `PackageProvider.cpp` / CMake | Register the Pakta C++ package and native linkage |
| `pages/Index.ets` | Add `PaktaFileJSBundleProvider`, retaining embedded fallback |

```bash title="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](https://github.com/pakta-team/rn-update/tree/main/Example): `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:

```json5 title="harmony/entry/oh-package.json5 · dependencies"
"pakta": "file:../../node_modules/rn-update/harmony/pakta.har"
```

```typescript title="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()],
};
```

```typescript title="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)];
}
```

```cmake title="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")
```

```cpp title="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)};
}
```

```typescript title="harmony/entry/src/main/ets/pages/Index.ets · imports"
import { PaktaFileJSBundleProvider } from 'pakta';
import { AnyJSBundleProvider, ResourceJSBundleProvider } from '@rnoh/react-native-openharmony';
```

```typescript title="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](/docs/expo) for its root layout and channel plugin.

## Self-hosting: configure SDK and CLI separately {#self-host}

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

```tsx title="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 title="PowerShell"
$env:RNU_SERVICE_URL = 'https://YOUR_HOST'
```

```bash title="Bash / zsh"
export RNU_SERVICE_URL=https://YOUR_HOST
```

## Manual linking: only when autolinking is unavailable {#manual-link}

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.

Older Android projects need both Gradle dependency entries and React Package registration:

```groovy title="android/settings.gradle"
include ':rn-update'
project(':rn-update').projectDir = new File(rootProject.projectDir, '../node_modules/rn-update/android')
```

```groovy title="android/app/build.gradle · dependencies"
implementation project(':rn-update')
```

In `MainApplication.java`, add `new UpdatePackage()` to the existing `getPackages()` list and import it:

```java title="MainApplication.java"
import cn.reactnative.modules.update.UpdatePackage;

@Override
protected List<ReactPackage> getPackages() {
  return Arrays.<ReactPackage>asList(
    new MainReactPackage(),
    new UpdatePackage()
  );
}
```

Keep every existing package and append only `new UpdatePackage()`; do not create a second React Host. After linking, still configure `UpdateContext.getBundleUrl(...)` as described in [Android bundle loading](#android).

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.

Pakta does not ship a standalone `RCTPakta.xcodeproj` for direct insertion into an old Xcode project. For RN <0.60 without CocoaPods, enable CocoaPods or upgrade React Native first. A true manual integration must include `ios/RCTPakta`, the shared C++ core, the `SSZipArchive` dependency and `pakta_build_time.txt` in the app target, followed by Release startup and native-package registration checks.

## Android activity restoration with react-native-screens {#android-activity}

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:

```kotlin title="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](https://github.com/software-mansion/react-native-screens#android).

## Optional: open a test update with a link {#deep-link}

An existing scanner can call [parseTestQrCode](/docs/api#function-parsetestqrcodeqrcode-string). 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":

```xml title="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 {#aab}

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. Pakta CLI extracts the `base` module by default; if the distributed build depends on additional splits, select them explicitly:

```bash title="Select AAB splits used by the distribution"
pakta uploadAab ./android/app/build/outputs/bundle/release/app-release.aab --splits config.xxhdpi,config.arm64_v8a
```

You can also use `--includeAllSplits` to create a registration APK containing every split. Verify resources and updates using the distributed build. Investigate resource packaging and the native version when a split lacks an asset.

## Next step {#verify}

Dependencies are installed, the CLI has selected the correct platform application, and native loading is configured. [Connect your app](/docs/integration), add the version panel, then build the native package.

Local Expo build flags follow the [official Expo CLI reference](https://docs.expo.dev/more/expo-cli/).
