Quickstart
~15 min setup

RELAYOTA SDK Guide

Push JS bundle updates to your React Native users instantly — no App Store review, no infrastructure to manage.

Before you start

React Native 0.71+ project
RELAYOTA accountSign up free →
Xcode 15+ (iOS) or Android Studio Hedgehog+ (Android)
1

Create an app & get your App ID

Every React Native app needs an App record in the dashboard. The App ID is used by the SDK to check for updates.

Go to AppsNew App, choose your platform, enter your bundle ID, and set the runtimeVersion (must match your native build — e.g. 1.0.0).

runtimeVersion must be an exact match — 1.0.0 and 1.0 are treated as different values. Use full semver strings (three parts) everywhere.

Copy the App ID from the app detail page — you'll need it in step 4.

2

Install the SDK

Install the npm package and its required peer dependencies.

bash
npm install relay-ota-react-native react-native-fs @react-native-async-storage/async-storage
bash
# iOS — link native modules
cd ios && pod install
3

Add the native bundle loader

The native module intercepts the JS bundle load so the updated bundle is used after an OTA restart.

Copy OtaBundleUpdater.h and OtaBundleUpdater.m from the SDK's src/native/ folder into your Xcode project.

AppDelegate.mm

objc
#import "OtaBundleUpdater.h"

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
  return [OtaBundleUpdater bundleURLForBridge:bridge];
}
If your project uses Swift/Objective-C bridging (AppDelegate.swift), wrap the method in an @objc extension and import the header via the bridging header.
4

Wrap your app with OtaProvider

OtaProvider holds update state, auto-checks on foreground, and exposes hooks to all children.

tsx
// App.tsx
import { OtaProvider, createRNFSFileSystemAdapter } from 'relay-ota-react-native';

const fileSystem = createRNFSFileSystemAdapter();

export default function App() {
  return (
    <OtaProvider
      fileSystem={fileSystem}
      config={{
        serverUrl: 'https://api.relayota.com', // ← API URL, not dashboard URL
        appId: 'YOUR_APP_ID',
        channel: 'production',
        platform: 'IOS',
        currentVersion: '0.0.0', // ← use 0.0.0 for fresh installs (see note below)
        runtimeVersion: '1.0.0', // ← must match the dashboard exactly
      }}
    >
      {/* rest of your app */}
    </OtaProvider>
  );
}

Config reference

serverUrlUse https://api.relayota.com — this is the managed API, not the web dashboard URL
appIdApp ID from the dashboard
channel"production" or "staging" (created automatically)
platform"IOS" or "ANDROID"
currentVersionSemver of the JS bundle currently running. Use "0.0.0" for fresh installs — the first release auto-assigns "1.0.0"
runtimeVersionMust match the dashboard App record exactly (full semver, e.g. "1.0.0" not "1.0")
checkOnForegroundAuto-check when app returns to foreground (default: true)
The first release you push is auto-assigned version 1.0.0 by the server. If your app's currentVersion is also 1.0.0, the server will not report an update (it only serves versions higher than currentVersion). Set currentVersion to 0.0.0 in the initial APK/IPA to ensure the first OTA release is delivered. The SDK automatically tracks which release was last applied and sends that version on subsequent checks — so you only need the low starting value in the initial build.
5

Handle updates in your UI

Use the built-in hooks to show update prompts, progress bars, or auto-apply silently.

Option A — useAutoUpdate (recommended)

tsx
import { useAutoUpdate } from 'relay-ota-react-native';

export function UpdateHandler() {
  const { isReady, isLoading, applyUpdate } = useAutoUpdate();

  if (isLoading) return <ActivityIndicator />;

  if (isReady) {
    return (
      <View style={styles.banner}>
        <Text>Update ready — restart to apply</Text>
        <Button onPress={applyUpdate} title="Restart" />
      </View>
    );
  }

  return null;
}

Option B — useOtaUpdate (full manual control)

tsx
import { useOtaUpdate } from 'relay-ota-react-native';

export function UpdateScreen() {
  const {
    status, progress, release,
    hasUpdate, isLoading, isReady,
    checkForUpdates, downloadUpdate, applyUpdate, rollback,
  } = useOtaUpdate();

  return (
    <View>
      <Text>Status: {status}</Text>
      {hasUpdate && <Button onPress={downloadUpdate} title={`Download v${release?.version}`} />}
      {isLoading && <ProgressBar progress={progress} />}
      {isReady && <Button onPress={applyUpdate} title="Restart & apply" />}
    </View>
  );
}

UpdateStatus values

idleNo check started yet
checkingPolling the server
up_to_dateAlready on latest
update_availableNew bundle available — call downloadUpdate()
downloadingBundle download in progress
readyBundle saved — call applyUpdate() to restart
errorCheck state.error for details
6

Push your first release

Build your JS bundle and upload it to the dashboard.

Go to ReleasesNew Release in the dashboard. Select your app, choose the channel (production), set the runtime version to match your OtaProvider config, then upload the bundle file.

Build the bundle first:

bash
# Android
npx react-native bundle \
  --platform android --dev false \
  --entry-file index.js \
  --bundle-output ./index.android.bundle

# iOS
npx react-native bundle \
  --platform ios --dev false \
  --entry-file index.js \
  --bundle-output ./main.jsbundle

Upload the generated index.android.bundle or main.jsbundle file via the dashboard release form. The server auto-assigns version numbers and handles delivery to devices.

Set the runtime version in the release form to exactly the same value as runtimeVersion in OtaProvider and the dashboard App record (e.g. 1.0.0). The SDK only applies releases whose runtime version matches exactly.

You're live

Monitor update adoption in Analytics. Use Disable on a release or call rollback() from the SDK to revert if needed.

Troubleshooting

Common issues and how to fix them. Click to expand.

The native files weren't registered. For iOS: confirm the .m file is added to the Xcode target and pod install ran. For Android: verify OtaBundleUpdaterPackage is added in getPackages() and the package name matches your source directory.

Ready to ship OTA updates?

Start your 14-day free trial and push your first OTA update in minutes.

Get started free