Documentation

Everything you need to integrate Upflux into your React Native or Capacitor app.

Introduction

Upflux is an over-the-air (OTA) update solution for React Native and Capacitor apps. It allows you to push JavaScript and asset updates directly to your users without going through the app store review process.

🚀 Why Upflux?

  • • Instant updates without app store approval
  • • Automatic rollback on errors
  • • Multi-deployment support (staging, production)
  • • Built-in analytics and monitoring
  • • Works with React Native and Capacitor

Quick Start

1. Install the SDK

bash
npm install @upfluxhq/react-native

2. Create upflux.json

Run the init command to scaffold your config file:

bash
upflux init

Or create upflux.json manually at your project root:

json
{
  "version": 1,
  "app": {
    "id": "your-app-id",
    "serverUrl": "https://api.upflux.in"
  },
  "deployments": {
    "production": {
      "clientKey": "upx_cli_prd_xxxxx",
      "checkOnLaunch": "always",
      "installMode": "immediate"
    },
    "development": {
      "clientKey": "upx_cli_dev_xxxxx",
      "installMode": "on_next_restart"
    }
  }
}

3. Initialize the SDK & Confirm Bundle

typescript
import React, { useEffect } from 'react';
import { Upflux } from '@upfluxhq/react-native';

export default function App() {
  useEffect(() => {
    // 1. Reads upflux.json — auto-selects 'development' in dev builds
    // and auto-confirms health on successful mount
    Upflux.init();
  }, []);

  return <YourApp />;
}

4. Check for Updates

With checkOnLaunch set in upflux.json, the SDK checks automatically on every launch — no extra code needed.

typescript
// Zero extra code needed when checkOnLaunch: 'always' (default)
await Upflux.init();

// Optional: intercept to show a loader, custom UI, or unblock splash screen
await Upflux.init('production', {
  onCheckComplete: (hasUpdate) => {
    if (!hasUpdate) setCheckingUpdate(false); // unblock splash immediately
  },
  onUpdateAvailable: async (info, actions) => {
    setLoading(true);
    const result = await actions.download();
    setLoading(false);
    await actions.apply(result);
  },
  onDownloadProgress: ({ percentage }) => setProgress(percentage),
  onUpdateQueued: (info) => console.log('Update ready on next launch:', info.releaseLabel),
  onRollback: ({ rolledBackFrom }) => console.warn('Rolled back from crashing release:', rolledBackFrom),
});

// Manual mode: set checkOnLaunch: 'never' in upflux.json
const upflux = Upflux.getInstance();
const updateInfo = await upflux.checkForUpdates();
if (updateInfo.updateAvailable) {
  const result = await upflux.downloadUpdate(updateInfo);
  if (result.success) await upflux.applyUpdate(result);
}

Installation

Upflux requires React Native 0.60 or higher. Install the SDK using your preferred package manager:

npm
npm install @upfluxhq/react-native
yarn
yarn add @upfluxhq/react-native
pnpm
pnpm add @upfluxhq/react-native

⚠️ Native Module Setup (Required)

You must configure your app to load OTA bundles. The native module is autolinked, but you need to add bundle loading logic.

Android (MainApplication.kt)
kotlin
import com.upflux.UpfluxModule

class MainApplication : Application(), ReactApplication {
  override val reactHost: ReactHost by lazy {
    val customBundlePath = UpfluxModule.getJSBundleFile(this)
    getDefaultReactHost(
      context = applicationContext,
      packageList = PackageList(this).packages,
      jsBundleFilePath = customBundlePath
    )
  }
}
Android (MainApplication.java - for Java projects)
java
import com.upflux.UpfluxModule;

@Override
protected String getJSBundleFile() {
  String customBundle = UpfluxModule.getJSBundleFile(this);
  if (customBundle != null) return customBundle;
  return super.getJSBundleFile();
}
iOS (AppDelegate.m - Objective-C)
objc
#import <upflux_react_native/UpfluxModule.h>

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
  NSURL *customBundle = [UpfluxModule bundleURL];
  if (customBundle) return customBundle;
  return [self bundleURL];
}
iOS (AppDelegate.swift - Swift projects)
swift
// Add to Bridging-Header.h:
// #import <upflux_react_native/UpfluxModule.h>

override func sourceURL(for bridge: RCTBridge) -> URL? {
  if let customBundle = UpfluxModule.bundleURL() {
    return customBundle
  }
  return bundleURL()
}

Install CocoaPods

cd ios && pod install

upflux.json

upflux.json is a project-root config file that replaces inline SDK configuration. It lets you define named deployment profiles and switch between them with a single argument.

Full Schema

json
{
  "version": 1,
  "app": {
    "id": "your-app-id",
    "buildNumber": 0,
    "runtimeVersion": "1.0.0",
    "serverUrl": "https://api.upflux.in"
  },
  "deployments": {
    "development": {
      "clientKey": "upx_cli_dev_xxxxx",
      "checkOnLaunch": "always",
      "installMode": "on_next_restart"
    },
    "staging": {
      "extends": "development",
      "clientKey": "upx_cli_stg_xxxxx"
    },
    "production": {
      "clientKey": "upx_cli_prd_xxxxx",
      "checkOnLaunch": "always",
      "installMode": "immediate"
    }
  }
}

Schema Reference

FieldRequiredDefaultDescription
version✓—Schema version — must be 1
app.id✓—Your Upflux App ID
app.serverUrl—https://api.upflux.inAPI server URL (self-hosted)
app.buildNumber—0Integer auto-incremented by CLI on every upflux release. Start at 0. Commit this file after each release.
app.runtimeVersion——Human-readable semver (e.g. '1.0.0'). Combined with buildNumber to form auto release labels like 1.0.0+6. Update manually when shipping a new native binary.
deployments.<name>.clientKey✓*—Read-only SDK key (upx_cli_*). Required unless inherited via extends
deployments.<name>.extends——Inherit settings from another profile name. Max depth: 3
deployments.<name>.checkOnLaunch—alwaysWhen to check for updates
deployments.<name>.installMode—on_next_restartWhen to apply a downloaded update

💡 Safe to commit

upflux.json is safe to commit to git.upx_cli_* client keys are read-only — they cannot publish releases. Only upx_pub_* publish keys (used by the CLI) should be kept secret.

Tip: Commit upflux.json after every release — the CLI auto-increments buildNumber and that change should be tracked in git.

Deployment Profiles

Each key under deployments is a named profile. The profile name is what you pass to Upflux.init().

checkOnLaunch

Controls when the SDK checks the server for a new release.

'always'

Check on every app open. Default.

'on_app_start'

Check on cold starts only (not foreground resumes).

'never'

No automatic checks. Use checkForUpdates() manually.

installMode

Controls when a downloaded update is applied.

'on_next_restart'

Download in the background. Update applies silently on the next app launch. Default. Best for non-critical updates.

'immediate'

Restart the app immediately after download. Best for critical hotfixes.

🤖 Auto Profile Selection

When no profile is passed to Upflux.init(), the SDK selects a profile automatically:

  1. process.env.UPFLUX_DEPLOYMENT — if set (useful in CI/CD)
  2. 'development' — when Metro's __DEV__ is true (dev build)
  3. 'production' — fallback for release builds

⚡ Auto-Update Flow

When checkOnLaunch is 'always' or 'on_app_start', the SDK runs the full check → download → apply cycle in the background after init() — no extra code needed.

With installMode: 'on_next_restart'

Downloads silently in background. Queues update. Activates on next cold start. User never interrupted.

With installMode: 'immediate'

Downloads and restarts the app immediately. Best for critical hotfixes.

Override with onUpdateAvailable callback to show loaders, dialogs, or control the flow manually.

Profile Inheritance

Use extends to inherit settings from another profile. The child profile's own keys always win over inherited values.

Without extends — repetitive

json
{
  "deployments": {
    "development": {
      "clientKey": "upx_cli_dev_xxx",
      "checkOnLaunch": "always",
      "installMode": "on_next_restart"
    },
    "staging": {
      "clientKey": "upx_cli_stg_xxx",
      "checkOnLaunch": "always",
      "installMode": "on_next_restart"
    }
  }
}

With extends — DRY

json
{
  "deployments": {
    "base": {
      "checkOnLaunch": "always",
      "installMode": "on_next_restart"
    },
    "development": {
      "extends": "base",
      "clientKey": "upx_cli_dev_xxx"
    },
    "staging": {
      "extends": "base",
      "clientKey": "upx_cli_stg_xxx"
    }
  }
}

Resolution Rules

  • • Child profile keys always win over inherited keys
  • • Maximum extends depth: 3 levels
  • • Circular references throw a clear error with the detected chain
  • • clientKey must be present after full resolution

Apps & Deployments

Upflux organizes your updates around Apps and Deployments.

📱 Apps

An app represents your mobile application. Each app has a unique App ID that you use to configure the SDK.

🚀 Deployments

Deployments are environments within your app. Common deployments include:

  • • Production - Your live app used by real users
  • • Staging - For testing before production

🔑 API Keys

Upflux uses two types of API keys:

  • • upx_pub_* - Publish Keys for CLI (releasing updates)
  • • upx_cli_* - Client Keys for SDKs (checking/downloading updates)

Releases

A release is a bundle of JavaScript code and assets that you push to your users.

Release Label

A unique identifier like v1.0.0 or v1.2.3-hotfix

Binary Version Range

Target specific app versions like 1.0.0 - 1.2.x

Rollout Percentage

Gradually roll out to 10%, 50%, or 100% of users

Mandatory Flag

Force users to update immediately if critical

Scheduled Release

Schedule releases for a specific date and time

Scheduled Releases

Schedule releases to go live at a specific date and time. Perfect for coordinating updates with marketing campaigns, feature launches, or maintenance windows.

📅 How Scheduled Releases Work

  1. Create a release with the --schedule flag
  2. The release is uploaded as a Draft
  3. Set the scheduled time in the Upflux Dashboard
  4. The release automatically goes live at the scheduled time

Creating a Scheduled Release

bash
# Create a draft release for scheduling
upflux release \
  --label v2.0.0 \
  --schedule

# Output:
# ✓ Release uploaded successfully!
#   Status: DRAFT
#   📅 Set schedule time in dashboard

✅ Use Cases

  • • Feature launches at specific times
  • • Coordinated global releases
  • • Low-traffic maintenance windows
  • • Marketing campaign coordination

💡 Tips

  • • Test with staging first
  • • Consider timezone differences
  • • Combine with rollout percentages
  • • Monitor after release goes live

Update Strategies

⚡ Immediate Updates

Download and apply updates as soon as they're available. Best for critical fixes.

typescript
const update = await upflux.checkForUpdates();
if (update.updateAvailable) {
  const result = await upflux.downloadUpdate(update);
  await upflux.applyUpdate(result); // Restarts immediately
}

🕐 Deferred Updates

Download in the background and apply on next app restart.

typescript
const update = await upflux.checkForUpdates();
if (update.updateAvailable) {
  await upflux.downloadUpdate(update);
  // Update applies on next app launch
}

Version Safety

Upflux includes built-in version guards to prevent bad updates from reaching devices.

💥 Crash Detection & Auto-Rollback

When your app renders successfully and executes Upflux.init(), the SDK automatically confirms the release as stable. If a fatal JS error, syntax failure, or missing native dependency crashes the app before init() can run, Upflux detects it on the next launch and automatically reverts to the previous stable bundle.

typescript
// App.tsx
useEffect(() => {
  Upflux.init('production', {
    onRollback: ({ rolledBackFrom, rolledBackTo }) => {
      // Notified when app auto-reverts a broken release
      console.warn('Auto-rolled back from ' + rolledBackFrom + ' to ' + (rolledBackTo ?? 'binary'));
    }
  });
}, []);

🛡️ Downgrade Protection & Build Number Tiebreakers

The server and SDK both ensure the incoming OTA release is strictly newer than the currently installed release. Full support for SemVer build metadata (+N) means labels like 2.1.5+6 are correctly evaluated as newer than 2.1.5+5.

typescript
// If device has 2.1.5+5 and server offers 2.1.5+6:
const info = await upflux.checkForUpdates();
// info.updateAvailable === true (newer build detected)

// If device has 2.1.5+6 and server has 2.1.5+5:
// info.updateAvailable === false (downgrade blocked)

🏪 Store Update Gate

When a release requires native changes (new permissions, new pods, updated binary), you can set a minBinaryVersion via the CLI. Devices below that version receive a storeUpdateRequired signal instead of the OTA bundle.

bash
# Publish a release that requires users to be on native v2.0.0+
upflux release \
  --label v2.0.0-ota \
  --min-binary-version 2.0.0 \
  --version-range ">=2.0.0"

Handle the response in your app:

typescript
const info = await upflux.checkForUpdates();

if (info.storeUpdateRequired) {
  // Device binary is too old — direct to App Store / Play Store
  showStoreUpdateAlert(info.requiredBinaryVersion);
} else if (info.updateAvailable) {
  // Normal OTA update flow
  const result = await upflux.downloadUpdate(info);
  await upflux.applyUpdate(result);
}

🔍 Automatic Native Change Detection

The Upflux CLI automatically detects changes in native files (build.gradle, AndroidManifest.xml, Podfile, package.json) using git diff since the last release and prompts you to set --min-binary-version automatically.

Configuration

✅ Recommended: upflux.json

Create upflux.json at your project root (run upflux init), then:

typescript
import { Upflux } from '@upfluxhq/react-native';

// Auto-selects 'development' in dev builds, 'production' in release
await Upflux.init();

// Or specify explicitly:
await Upflux.init('production');

Profile auto-selection priority: UPFLUX_DEPLOYMENT env var → __DEV__ → 'production'

UpfluxConfig Interface

typescript
interface UpfluxConfig {
  appId: string;       // Your Upflux app ID
  deployment: string;  // Target deployment (e.g., 'production')
  clientKey: string;   // Client API key (upx_cli_xxx)
  serverUrl: string;   // Server URL
  collectDeviceInfo?: boolean; // Collect device info (default: true)
}

init()

Initializes the SDK, resolves which bundle to load, and automatically runs the update check + download + apply cycle in the background based on checkOnLaunch from your upflux.json.

Static factory (recommended)

typescript
// Reads upflux.json, auto-selects profile, runs update check in background
const { instance, startupBundle } = await Upflux.init();

// With explicit profile
await Upflux.init('production');

// With lifecycle callbacks — intercept any stage
await Upflux.init('production', {
  // Unblock splash screen immediately when check finishes
  onCheckComplete: (hasUpdate) => {
    if (!hasUpdate) setCheckingUpdate(false);
  },
  onUpdateAvailable: async (info, actions) => {
    // You control download + apply timing
    setLoading(true);
    const result = await actions.download();
    setLoading(false);
    await actions.apply(result);
  },
  onDownloadProgress: ({ percentage, bytesDownloaded, totalBytes }) => {
    setProgress(percentage);
  },
  onUpdateQueued: (info) => showToast('Update ready on next launch: ' + info.releaseLabel),
  onUpdateApplied: () => console.log('Restarting with new bundle'),
  onRollback: ({ rolledBackFrom, rolledBackTo }) => {
    console.warn('Rolled back from ' + rolledBackFrom + ' to ' + rolledBackTo);
  },
  onError: (err) => console.warn('Update failed:', err.message),
});

checkOnLaunch behaviour

ValueWhat init() does automatically
'always' (default)Check → download → apply on every launch
'on_app_start'Check → download → apply on cold starts only (not foreground resumes)
'never'Nothing automatic — call checkForUpdates() manually

installMode behaviour

ValueEffect after download
'on_next_restart' (default)Queued silently. result.deferred = true. Activates on next cold start. User never interrupted.
'immediate'App restarts immediately after download. Best for critical hotfixes.

checkForUpdates()

Checks the server for available updates matching the device and app configuration.

typescript
const updateInfo = await upflux.checkForUpdates();

// Returns UpdateInfo:
// {
//   updateAvailable: boolean,
//   storeUpdateRequired?: boolean,    // true = device needs store update first
//   requiredBinaryVersion?: string,   // minimum binary version required
//   releaseLabel: string,
//   downloadUrl: string,
//   mandatory: boolean,
//   description: string
// }

downloadUpdate()

Downloads the update bundle and assets to local storage with concurrent transfer and configurable network timeout.

typescript
const result = await upflux.downloadUpdate(updateInfo, {
  timeout: 45000, // Optional timeout in ms (default: 60000ms / 60s)
  onProgress: ({ percentage, bytesDownloaded, totalBytes }) => {
    // 0–60% JS bundle, 60–100% assets
    console.log('Progress: ' + percentage + '% (' + bytesDownloaded + '/' + totalBytes + ' bytes)');
  }
});

// Returns DownloadResult:
// {
//   success: boolean,
//   bundlePath: string,
//   assetPaths: string[],
//   error?: string
// }

⚡ 4x Parallel Asset Downloads

Asset dependencies are downloaded concurrently using an internal 4-worker pool instead of serial transfers. A built-in 60-second abort timeout prevents stalled connections on unstable mobile networks.

applyUpdate()

Applies the downloaded update and restarts/reloads the app.

typescript
const applyResult = await upflux.applyUpdate(downloadResult);

// Returns ApplyResult:
// {
//   applied: boolean,
//   bundlePath: string | null,
//   deferred?: boolean, // true if installMode is 'on_next_restart'
//   error?: string
// }
// Note: App will restart before this returns if immediate

Other Methods

pruneOldBundles(keepLabels)

Manually cleans up older bundle directories from local storage while preserving specified releases.

typescript
import { pruneOldBundles } from '@upfluxhq/react-native';

await pruneOldBundles(['2.1.5+6', '2.1.5+5']);

clearUpdate()

Clears the current update and reverts to the default bundle.

await upflux.clearUpdate();

isInitialized()

Returns whether the SDK has been initialized.

const initialized = upflux.isInitialized(); // boolean

getStartupBundle()

Returns startup bundle info from init() including rollback status.

const bundle = upflux.getStartupBundle();

getConfig()

Returns the current SDK configuration.

const config = upflux.getConfig();

destroy()

Destroys the SDK instance and cleans up resources.

upflux.destroy();

upflux init

Interactively scaffold upflux.json in your project root. Prompts for App ID, server URL, deployment profile names, and client keys.

bash
upflux init

# Output:
# 🚀 Upflux Project Setup
#
# ? App ID: my-app
# ? Server URL (press Enter for default):
# ? Deployment profiles to create: production, staging, development
# ? Client key for "production": upx_cli_prd_xxxxx
# ? Client key for "staging": upx_cli_stg_xxxxx
# ? Client key for "development": upx_cli_dev_xxxxx
#
# ✓ Created upflux.json

# Overwrite an existing upflux.json:
upflux init --force

💡 upflux.json is safe to commit. Client keys (upx_cli_*) are read-only and cannot publish releases. Only publish keys (upx_pub_*) must be kept secret.

upflux login

@upfluxhq/cli on npm
npm install -g @upfluxhq/cli

Authenticate with your publish key to enable releasing updates. Supports multiple credentials for different deployments.

bash
# Non-interactive (recommended)
upflux login --key <publishKey>

# Example with multiple deployments
upflux login --key upx_pub_staging_xxxxx
upflux login --key upx_pub_production_xxxxx

# Credentials are stored locally in .upflux file

⚠️ Never commit the .upflux file to version control. It contains your publish keys. Add it to your .gitignore.

upflux release

Create and publish a new release to your deployment.

Auto-increment (Recommended)

Reads buildNumber from upflux.json, increments it, and generates a label like 1.0.0+6 automatically:

bash
# No --label needed — buildNumber auto-increments in upflux.json
upflux release \
  --platform all \
  --version-range ">=1.0.0"

Manual Label

Pass --label explicitly for a custom semver tag. buildNumber still increments for tracking:

bash
upflux release \
  --label v2.0.0 \
  --platform all \
  --version-range ">=2.0.0" \
  --rollout 100

Platform-specific (hotfixes)

bash
# iOS hotfix only
upflux release --platform ios --mandatory

# Android hotfix only
upflux release --platform android --mandatory

Capacitor

Build your web assets first, then release:

bash
npm run build

# Auto-increment release
upflux release --rollout 100

# Or with explicit label
upflux release --label v1.0.0 --rollout 100

Options

FlagRequiredDescription
-l, --labelOptionalRelease label (e.g., v1.0.0). Omit to auto-increment buildNumber from upflux.json and generate a label like 1.0.0+6
-p, --platformOptionalTarget platform: all, ios, or android (default: all for React Native)
-v, --version-rangeRecommendedBinary version range (e.g., >=1.0.0)
--min-binary-versionOptionalMinimum native binary version required. Devices below this receive a store update prompt instead of the OTA.
-m, --mandatoryOptionalForce users to update immediately
-r, --rolloutOptionalRollout percentage (0-100, default: 100)
-e, --entry-fileOptionalEntry file for bundler (default: index.js)
-o, --output-dirOptionalOutput directory for bundle (default: ./dist)
-b, --bundleOptionalSkip bundling, use existing bundle file
--skip-bundleOptionalSkip bundling, use existing files in output dir
--skip-native-checkOptionalSkip automatic native change detection (useful in CI/CD)
--scheduleOptionalCreate as draft, set schedule time in dashboard
--capacitorOptionalForce Capacitor mode (auto-detected if capacitor.config exists)
--web-dirOptionalWeb directory for Capacitor (default: from config or www)

upflux status

Check the status of your deployments and recent releases.

bash
upflux status

# Shows:
# - Current deployment
# - Latest releases
# - Active devices count
# - Update success rate

upflux logout

Remove stored credentials from the local machine.

bash
# Interactive selection to remove one credential
upflux logout

# Remove all credentials at once
upflux logout --all

upflux generate-manifest

Fetch and display the manifest for a specific release.

bash
# Display manifest for a release
upflux generate-manifest --release-id <releaseId>

# Save manifest to a file
upflux generate-manifest --release-id <releaseId> --output manifest.json