Connect your app
Configure the authenticated update client, build adapter and native bundle loading before testing an OTA update.
Before connecting
Use this page after agent setup, manual setup, or connecting an existing server. Both setup paths use the same app integration and OTA verification.
Before editing, have the required packages installed, a deployed update server base URL, and its registered client API key when authentication is enabled. Managed providers require this key. Existing v0 apps must follow Move from v0 to v1 for the migration steps.
Configure the update client
Use HotUpdater.init() and a custom update flow so your app chooses when to
check, download and restart. Initialization configures the client and handles
native startup verification; it does not check for or download an update.
Call init once, at the top level of its own module, outside React
components. It returns the app's HotUpdater instance; export it, and import it
wherever the app uses HotUpdater. The SDK handles native launch verification
internally.
import { HotUpdater } from "@hot-updater/react-native";
import { Alert } from "react-native";
export function reportUpdateError(error: unknown) {
console.error("Hot Updater:", error);
Alert.alert("Update failed", "Please try again.");
}
export const hotUpdater = HotUpdater.init({
baseURL: "https://your-update-server-url",
requestHeaders: {
"x-api-key": "<your-registered-client-api-key>",
},
onError: reportUpdateError,
});This example has one button: Check and apply update. Tapping it checks for an update, downloads it, then restarts the app after a successful download.
import { useState } from "react";
import { Button, Text, View } from "react-native";
import { hotUpdater, reportUpdateError } from "./src/hotUpdater";
export default function App() {
const [isUpdating, setIsUpdating] = useState(false);
async function applyUpdate() {
setIsUpdating(true);
try {
const update = await hotUpdater.checkForUpdate({
updateStrategy: "appVersion",
});
if (!update) {
return;
}
const downloaded = await update.updateBundle();
if (!downloaded) {
throw new Error("Could not prepare the update");
}
await hotUpdater.reload();
} catch (error) {
reportUpdateError(error);
} finally {
setIsUpdating(false);
}
}
return (
<View>
<Text>OTA baseline</Text>
<Text>Update ID: {hotUpdater.getBundleId()}</Text>
<Button
title={isUpdating ? "Updating…" : "Check and apply update"}
disabled={isUpdating}
onPress={() => void applyUpdate()}
/>
</View>
);
}Export your component normally. You do not need an effect, a readiness flag or
an onNotifyAppReady callback for setup. Each init call creates a separate
instance with its own plugins, so keep the one call in src/hotUpdater.ts.
Client plugins
add their APIs to the same instance.
The onError handler in init reports lifecycle and update-check errors.
The button's try/catch handles rejected operations, including download and
restart failures. Both use a reporter that accepts unknown without casting
or rethrowing callback errors. Connect it to your app's error-reporting service
as needed.
If checkForUpdate returns null, stop the flow. It can mean no update
was selected or a check error was already reported; do not display "up to date"
based on that value alone.
The button explicitly applies the update now, so it restarts for both normal
and required updates. For background downloads or a separate restart prompt,
see Control update timing. To check when the app
starts instead, with an optional loading screen, wrap the root with
hotUpdater.wrap:
export default hotUpdater.wrap({ updateStrategy: "appVersion" })(App).
Replace the URL and client-key placeholders using your verified provider setup.
Keep any provider base path; the SDK appends the catalog and artifact routes.
Use the same updateStrategy in checkForUpdate and hot-updater.config.ts.
appVersion is the default; choose
fingerprint when you want to track
native compatibility inputs.
The client key is intended for the app. Provider tokens, service-role keys, admin bearer tokens and signing private keys stay in the deployment environment or server. Never substitute one of those credentials for the client key.
Supply values to native and OTA builds
.env.hotupdater configures local CLI/provider access; it is not automatically
loaded into your app. Connect the server URL and client key to your project's
app build configuration, and verify that both native builds and OTA bundle
builds receive them, including CI or EAS.
For Expo, replace the example's configuration with public app variables after checking that they are present:
const baseURL = process.env.EXPO_PUBLIC_HOT_UPDATER_BASE_URL;
const clientApiKey = process.env.EXPO_PUBLIC_HOT_UPDATER_API_KEY;
if (!baseURL || !clientApiKey) {
throw new Error("Missing Hot Updater URL or client API key");
}
export const hotUpdater = HotUpdater.init({
baseURL,
requestHeaders: { "x-api-key": clientApiKey },
onError: reportUpdateError,
});For bare React Native and Rock, use the project's existing build-time configuration mechanism. Validate required values in the native and OTA build pipelines too. A missing URL or key must not silently disable initialization.
Reporting and custom update timing
To report launches and updates to Insights, add the
Insights plugin to plugins:
import { HotUpdater } from "@hot-updater/react-native";
import { HotUpdater, insights } from "@hot-updater/react-native";
export const hotUpdater = HotUpdater.init({
baseURL: "https://your-update-server-url",
requestHeaders: {
"x-api-key": "<your-registered-client-api-key>",
},
onError: reportUpdateError,
plugins: [insights()],
});The managed servers record these reports; a custom server needs the
insights() server plugin.
Without the client plugin, the app sends no reports. Native readiness and
received server reports are separate checks; confirm both after your first OTA.
Use the update store to render progress within your own UI. Your update flow can defer reload until navigation is idle or the user has saved their work.
Build configuration
Setup configures bundling through the build field in hot-updater.config.ts.
Keep the selected build adapter alongside your existing storage, database and
plugins settings.
Use the Bare build adapter for standard React Native CLI projects:
import { bare } from "@hot-updater/bare";
import { defineConfig } from "hot-updater";
export default defineConfig({
build: bare({
enableHermes: true,
}),
// ... storage, database, plugins, and updateStrategy
});No additional Babel plugin setup is required.
Native code setup
Apply the marked Hot Updater changes to your existing native files. Preserve your app's package/module names, registered packages and React Native bootstrap code. Use the variant matching your project; do not replace the entire native file with a sample application's boilerplate.
If you're using Expo with the plugin configured above, this step is handled automatically during prebuild.
Android
For React Native 0.82 and above, modify your MainApplication.kt:
package com.hotupdaterexample
import android.app.Application
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.hotupdater.HotUpdater
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
context = applicationContext,
packageList =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
},
jsBundleFilePath = if (BuildConfig.DEBUG) {
null
} else {
HotUpdater.getJSBundleFile(applicationContext)
},
)
}
override fun onCreate() {
super.onCreate()
loadReactNative(this)
}
}iOS
For Swift projects, modify your AppDelegate.swift:
import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import HotUpdater
@main
class AppDelegate: RCTAppDelegate {
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
self.moduleName = "HotUpdaterExample"
self.dependencyProvider = RCTAppDependencyProvider()
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = [:]
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
#else
Bundle.main.url(forResource: "main", withExtension: "jsbundle")
HotUpdater.bundleURL()
#endif
}
}For brownfield apps where the JS bundle is embedded in an XCFramework, you can
pass a custom Bundle instance to look up the JavaScript bundle resource:
HotUpdater.bundleURL(bundle: xcframeworkBundle) in Swift or [HotUpdater bundleURLWithBundle:xcframeworkBundle] in Objective-C.
Choose the native test channel
Use a dedicated channel such as ota-test for the first release-build test.
The Expo config above already selects it. For bare React Native or Rock, after
the native files exist, run:
npx hot-updater channel set ota-testChanging the embedded default channel requires a new native build. See Default Channel Setup for custom native paths. Before a store release, choose the channel that binary should use and rebuild.
Prepare signing and compatibility
If this app uses bundle signing, embed the matching public key before building. Signing is opt-in; server provisioning alone does not enable it. Preserve an existing signer and its public key.
For appVersion, record the native app version that the OTA will target. For
fingerprint, generate and embed the fingerprint before the native build using
the fingerprint workflow.
Verify the integration
Run local and server diagnostics from the app directory:
npx hot-updater doctor --json --server-base-url https://your-update-server-urlResolve reported setup issues, then continue to Test an OTA update. That test builds the native app in Release mode and proves that a changed screen and asset reach the device. Doctor checks configuration and server version; it does not prove OTA delivery.