Skip to content

Start here

Getting started

Install the core module, an OTA adapter, and a sink, wire ReleaseHealth.init, and call markHealthy once your first screen is interactive.

You need three pieces: the core engine, one adapter for your OTA vendor, and at least one sink to export events. The core runs the state machine regardless of which adapter and sink you pick.

Install

For expo-updates (EAS Update), posting events to your own endpoint:

npm install react-native-release-health @release-health/adapter-expo-updates @release-health/sink-http

Using hot-updater instead? Swap the adapter:

npm install react-native-release-health @release-health/adapter-hot-updater @release-health/sink-http

Requirements

  • React Native 0.76 or newer on the New Architecture (bridgeless). The core is a TurboModule; the legacy bridge is not supported.
  • iOS and Android. Expo apps work without a config plugin.
  • The adapter's vendor SDK as a peer: expo-updates 55+ or @hot-updater/react-native 0.35+.

Wire it up

Call init once, as early as possible, then call markHealthy() when your first screen is genuinely usable.

import { useEffect } from 'react';
import { ReleaseHealth, useReleaseHealth } from 'react-native-release-health';
import { expoUpdatesAdapter } from '@release-health/adapter-expo-updates';
import { httpSink } from '@release-health/sink-http';
 
ReleaseHealth.init({
  adapter: expoUpdatesAdapter(),
  sinks: [httpSink({ url: 'https://telemetry.example.com/release-health' })],
  healthyTimeoutMs: 15000, // markHealthy() deadline after a fresh update
  crashLoopThreshold: 2,   // failed launches before rollback is recommended
});
 
function App() {
  // status: 'stable' | 'probation' | 'healthy' | 'suspect' | 'failed' | ...
  const { status, activeUpdateId } = useReleaseHealth();
 
  useEffect(() => {
    // Call when your first screen is actually usable, not merely rendered.
    ReleaseHealth.markHealthy();
  }, []);
 
  useEffect(
    () =>
      ReleaseHealth.onRollbackRecommended(({ updateId, reason }) => {
        // Page your on-call, disable the release server-side, or let
        // autoRollback handle it where the adapter supports rollback.
      }),
    []
  );
 
  return <YourApp />;
}

The hot-updater adapter wires into HotUpdater.wrap rather than being passed inline; see the hot-updater adapter for the exact wiring.

Where markHealthy goes

markHealthy() placement is load-bearing. Call it when the first screen is genuinely interactive: data loaded, navigation responsive. Calling it from a splash screen, or unconditionally at mount, accepts a broken-but-rendering update and defeats probation. Never calling it on some code path leaves a healthy update parked at suspect until the next relaunch.

Verify in release builds

__DEV__ short-circuits probation, so nothing is verified in development. To watch the state machine run, use a release build and a real (or deliberately broken) OTA update. The repo ships two end-to-end runbooks that do exactly this: demo.md for expo-updates and demo-hot-updater.md for hot-updater.

Where next

  • Core API: every init option, markHealthy, onRollbackRecommended, notifyReload, and the useReleaseHealth hook.
  • Events and failure detection: the typed event stream and how the crash-loop verdict is reached.
  • Limitations: what the heuristic can and cannot detect. Read this before relying on it.