Measure logo

Over-the-air updates

Symbolicate crashes in over-the-air updates by uploading the matching symbols for each patch.

Over-the-air (OTA) updates let you ship changes without a full native app release. When a crash happens in a patched bundle, Measure needs the matching sourcemap to symbolicate the stack trace.

React Native

Each OTA update needs its own sourcemap, tied to a patch ID so Measure can match a crash to the exact bundle it came from. How you set that patch ID depends on whether you're on Expo.

Set a patch version

Alongside the patch ID, you can attach an optional patch version, a human-readable label for the OTA update. The patch ID is a UUID Measure uses internally to match a crash to its sourcemap; the patch version is what you read in the dashboard to tell one release apart from another.

Set it in two places for each OTA update, using the same value:

  • In MeasureConfig via the patchVersion field, so every event and crash from the patched bundle is tagged with it.
  • In the upload_patch.sh script via the --patch_version flag, so the uploaded sourcemap carries the same label.
new MeasureConfig({
  patchVersion: "v1.2.3-hotfix",
})

On Expo, set the patch version to the same message you pass to eas update --message. Using one string in both places lets you match a release in Expo to the same release in Measure at a glance.

eas update --branch production --message "v1.2.3-hotfix"

Expo

The Metro plugin sets the patch ID for you. Export with source maps and upload them after every deploy.

Step 1: Add withMeasureConfig to metro.config.js

The plugin injects a patch ID into each bundle at build time, with no changes to your SDK initialization code.

const { getDefaultConfig } = require('expo/metro-config');
const { withMeasureConfig } = require('@measuresh/react-native/metro');

const config = getDefaultConfig(__dirname);
module.exports = withMeasureConfig(config);

Step 2: Export with source maps

npx expo export --platform all --source-maps --output-dir dist

The export produces .hbc.map files under dist/_expo/static/js/ios/ and dist/_expo/static/js/android/.

Step 3: Upload the sourcemaps

Run upload_patch.sh after deploying each OTA update. The plugin already put the patch ID in the bundle, so the script reads it straight from the sourcemap. The script ships with the SDK package.

./node_modules/@measuresh/react-native/scripts/upload_patch.sh \
  --api_key "your-api-key" \
  --api_url "https://your-measure-url" \
  --path_to_sourcemap "./dist/_expo/static/js/ios/entry-abc123.hbc.map" \
  --patch_version "v1.2.3-hotfix"

Manual

Without the Metro plugin, generate the patch ID yourself and pass it to MeasureConfig.

Step 1: Generate a patch ID

Generate a UUID v4 for the patch. Use any UUID library, or the shell command below:

uuidgen | tr '[:upper:]' '[:lower:]'

Step 2: Pass the patch ID to MeasureConfig

import { Measure, MeasureConfig } from '@measuresh/react-native';

Measure.init({
  config: new MeasureConfig({
    autoStart: true,
    patchId: 'your-patch-uuid',
    patchVersion: 'v1.2.3-hotfix',
  }),
});

The patchId field must match the UUID you pass to the upload script.

Step 3: Generate the sourcemap

Generate the JavaScript sourcemap when you build the OTA bundle. The exact command depends on your OTA provider. For a manual React Native bundle:

npx react-native bundle \
  --platform ios \
  --dev false \
  --entry-file index.js \
  --bundle-output main.jsbundle \
  --sourcemap-output main.jsbundle.map

Step 4: Upload the sourcemap

Pass the sourcemap and the patch ID you set to upload_patch.sh:

./node_modules/@measuresh/react-native/scripts/upload_patch.sh \
  --api_key "your-api-key" \
  --api_url "https://your-measure-url" \
  --path_to_sourcemap "./path/to/main.jsbundle.map" \
  --patch_id "your-patch-uuid" \
  --patch_version "v1.2.3-hotfix"

Flutter

Flutter ships over-the-air updates through Shorebird. Support for symbolicating Shorebird patches is coming soon.

Think this page can be better?

Open an issue