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
npm install @upfluxhq/react-native2. Create upflux.json
Run the init command to scaffold your config file:
upflux initOr create upflux.json manually at your project root:
{
"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
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.
// 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 install @upfluxhq/react-nativeyarn add @upfluxhq/react-nativepnpm 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)
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)
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)
#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)
// 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 installupflux.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
{
"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
| Field | Required | Default | Description |
|---|---|---|---|
version | ✓ | — | Schema version — must be 1 |
app.id | ✓ | — | Your Upflux App ID |
app.serverUrl | — | https://api.upflux.in | API server URL (self-hosted) |
app.buildNumber | — | 0 | Integer 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 | — | always | When to check for updates |
deployments.<name>.installMode | — | on_next_restart | When 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:
process.env.UPFLUX_DEPLOYMENT— if set (useful in CI/CD)'development'— when Metro's__DEV__istrue(dev build)'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
{
"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
{
"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
- •
clientKeymust 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
- Create a release with the
--scheduleflag - The release is uploaded as a Draft
- Set the scheduled time in the Upflux Dashboard
- The release automatically goes live at the scheduled time
Creating a Scheduled Release
# 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.
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.
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.
// 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.
// 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.
# 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:
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:
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
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)
// 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
| Value | What 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
| Value | Effect 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.
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.
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.
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 immediateOther Methods
pruneOldBundles(keepLabels)
Manually cleans up older bundle directories from local storage while preserving specified releases.
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(); // booleangetStartupBundle()
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.
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
npm install -g @upfluxhq/cliAuthenticate with your publish key to enable releasing updates. Supports multiple credentials for different deployments.
# 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:
# 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:
upflux release \
--label v2.0.0 \
--platform all \
--version-range ">=2.0.0" \
--rollout 100Platform-specific (hotfixes)
# iOS hotfix only
upflux release --platform ios --mandatory
# Android hotfix only
upflux release --platform android --mandatoryCapacitor
Build your web assets first, then release:
npm run build
# Auto-increment release
upflux release --rollout 100
# Or with explicit label
upflux release --label v1.0.0 --rollout 100Options
| Flag | Required | Description |
|---|---|---|
-l, --label | Optional | Release label (e.g., v1.0.0). Omit to auto-increment buildNumber from upflux.json and generate a label like 1.0.0+6 |
-p, --platform | Optional | Target platform: all, ios, or android (default: all for React Native) |
-v, --version-range | Recommended | Binary version range (e.g., >=1.0.0) |
--min-binary-version | Optional | Minimum native binary version required. Devices below this receive a store update prompt instead of the OTA. |
-m, --mandatory | Optional | Force users to update immediately |
-r, --rollout | Optional | Rollout percentage (0-100, default: 100) |
-e, --entry-file | Optional | Entry file for bundler (default: index.js) |
-o, --output-dir | Optional | Output directory for bundle (default: ./dist) |
-b, --bundle | Optional | Skip bundling, use existing bundle file |
--skip-bundle | Optional | Skip bundling, use existing files in output dir |
--skip-native-check | Optional | Skip automatic native change detection (useful in CI/CD) |
--schedule | Optional | Create as draft, set schedule time in dashboard |
--capacitor | Optional | Force Capacitor mode (auto-detected if capacitor.config exists) |
--web-dir | Optional | Web directory for Capacitor (default: from config or www) |
upflux status
Check the status of your deployments and recent releases.
upflux status
# Shows:
# - Current deployment
# - Latest releases
# - Active devices count
# - Update success rateupflux logout
Remove stored credentials from the local machine.
# Interactive selection to remove one credential
upflux logout
# Remove all credentials at once
upflux logout --allupflux generate-manifest
Fetch and display the manifest for a specific release.
# 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