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
fiOver 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 distThe 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.mapUpload 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 theupload_dsyms.shscript 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