Measure logo
Features

Uploading Symbols

Upload ProGuard/R8 mapping files, dSYM files and JavaScript sourcemaps to symbolicate stack traces from crashes, ANRs and errors in Android, iOS, Flutter and React Native apps.

Stack traces from crashes, ANRs and errors may be obfuscated or contain memory addresses. To convert the stack traces to a human-readable format, you need to upload the mapping or symbol files based on the platform.

Android

If you are using ProGuard or R8 to obfuscate your code, you need to upload the mapping files to de-obfuscate the stack traces. Measure's Android Gradle Plugin automatically uploads the ProGuard/R8 mapping file to the Measure server when you run an assemble gradle task.

iOS

To symbolicate stack traces for iOS, you need to upload the dSYM files to map the memory addresses to a human-readable format. There are two ways to upload dSYM files:

Using Shell Script

Run the upload_dsym_manual.sh script to manually upload dSYM files after building your app.

./upload_dsym_manual.sh <path_to_dsym_folder> <api_url> <api_key> <version_name> <version_code> <app_unique_id> <build_size> [custom_headers]

Using XCArchive

Run the upload_dsym_xcarchive.sh script to automatically upload dSYMs using xcarchive file. This script automatically extracts all necessary build metadata (version, build size, dSYM paths, etc.) directly from the generated .xcarchive. the ipa file is necessary to generate the build size info. If the ipa_path is not provided, the script uses the application binary to generate build size.

./upload_dsym_xcarchive.sh <path_to_xcarchive> <api_url> <api_key> [custom_headers] [ipa_path]

If you are using Build Phases to upload DSYMs, make sure to upload DSYMs only for release builds.

React Native

React Native apps require sourcemaps to symbolicate JavaScript stack traces. The setup differs slightly between Android and iOS.

Android

The Measure Android Gradle Plugin automatically captures and uploads the composed JavaScript sourcemap when you run assembleRelease or bundleRelease. No additional steps are required.

Always use the sourcemap from the Gradle build output. Generating the sourcemap separately (e.g. via npx react-native bundle) produces a different bundle than what is embedded in the APK and will result in incorrect symbolication.

iOS

Add the upload_build_phase.sh script as a Run Script Build Phase in Xcode. It automatically uploads dSYM files and JavaScript sourcemaps in one step.

Step 1 — Enable sourcemap generation

In Xcode, open your target → Build Phases → "Bundle React Native code and images" and add this line at the top of the script:

export SOURCEMAP_FILE="$(pwd)/main.jsbundle.map"

Step 2 — Add the upload build phase

In Xcode, add a new Run Script Build Phase after the "Bundle React Native code and images" phase with the following content:

"${SRCROOT}/../node_modules/@measuresh/react-native/scripts/upload_build_phase.sh" \
  "https://your-api-url.measure.sh" \
  "your-api-key"

Replace the API URL and API key with your values from the Measure dashboard.

Always use the sourcemap generated by the Xcode build (via SOURCEMAP_FILE). Generating the sourcemap manually produces a different bundle than what is embedded in the app and will result in incorrect symbolication.

The script runs on every build in whatever configuration you add it to. To restrict it to Archive builds only, wrap the script body in:

if [ "$ACTION" = "archive" ]; then
  # script content here
fi

Over the Air Update Symbolication

Over-The-Air (OTA) updates let you ship JavaScript changes without a full native app release. When a crash occurs in a patched bundle, Measure needs the corresponding sourcemap to symbolicate the JavaScript stack trace.

The patch ID can be set in two ways:

  • Automated — The withMeasureConfig() Metro plugin automatically manages the patch ID for each build.

  • Manual — You generate the patch ID yourself and pass it to MeasureConfig.

Automated - Metro Plugin (Expo)

With this approach you only need to update the Metro config, the patch ID is injected into the bundle at build time, with no changes to your SDK initialisation code.

Step 1 - Add withMeasureConfig to metro.config.js

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/. Upload these after deploying the OTA update.

Always upload the sourcemaps from the same expo export run that produced the bundle. Generating the sourcemap separately produces a different bundle and will result in incorrect symbolication.

Manual

Use this approach when you manage the OTA update yourself and do not use the Expo/Metro build pipeline.

Step 1 — Generate a patch ID

Generate a UUID v4 for the patch. You can use any UUID library or the following shell command:

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', // optional human-readable label
  }),
});

The patchId field must match the UUID you pass to the upload script. patchVersion is an optional human-readable label shown alongside the patch ID in the dashboard.

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

Upload the Sourcemap

Run upload_patch.sh after deploying each OTA update. The script is included with the SDK package.

Automated mode

Use this when the bundle was built with withMeasureConfig(). The script reads the patch ID directly from the sourcemap file.

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

Manual mode

Use this when you set patchId manually in MeasureConfig.

./node_modules/@measuresh/react-native/scripts/upload_patch.sh \
  "your-api-key" \
  "https://your-measure-url" \
  "./path/to/main.jsbundle.map" \
  "your-patch-uuid"

Flutter

When obfuscating your Flutter app using --obfuscate and --split-debug-info options:

  • Android — The Measure Android Gradle Plugin automatically uploads the required mapping files.
  • iOS — You need to upload the dSYM files as described in the iOS section above. After building with flutter build ipa, run the upload_dsyms.sh script using the IPA path and the path to the dSYM folder (typically under /build/ios/Release-iphoneos/)

Think this page can be better?

Open an issue