| name | symbolication |
| description | Automate symbol uploads for iOS dSYMs, Android ProGuard/R8 mapping files, NDK native symbols, Dart obfuscation symbols, and Hermes source maps across Crashlytics, Sentry, and Datadog. Use when crashes show as addresses, unreadable class names, or minified JS frames. |
Symbolication Pipelines
Instructions
A crash without symbols is noise. Treat symbol upload as a release-blocker, automate it in CI, and verify symbols are resolvable before each release is promoted.
1. Why Symbols Matter
- iOS ships stripped binaries; stack traces contain addresses that must be resolved via dSYMs.
- Android release builds run R8 which renames classes and methods; mapping files reverse the renaming. NDK libraries ship stripped;
.so.sym files provide native frames.
- Flutter release builds are AOT-compiled Dart; with
--split-debug-info the debug symbols are emitted as a separate directory.
- React Native minifies JS and (with Hermes) compiles to bytecode; source maps are needed to recover JS frames.
2. iOS -- dSYMs
Xcode build settings:
DEBUG_INFORMATION_FORMAT = dwarf-with-dsym for Release.
STRIP_INSTALLED_PRODUCT = YES, COPY_PHASE_STRIP = NO.
ENABLE_BITCODE = NO (bitcode is deprecated since Xcode 14).
Automated upload via Fastlane:
lane :upload_symbols do
dsym_zip = "build/MyApp.xcarchive/dSYMs"
upload_symbols_to_crashlytics(
dsym_path: dsym_zip,
gsp_path: "ios/MyApp/GoogleService-Info.plist"
)
sentry_upload_dif(
auth_token: ENV["SENTRY_AUTH_TOKEN"],
org_slug: "my-org",
project_slug: "my-app-ios",
path: dsym_zip,
include_sources: true
)
end
If you enable App Store phased release, download dSYMs from App Store Connect within 24 hours (fastlane download_dsyms) because bitcode-recompiled binaries have different UUIDs.
3. Android -- Mapping Files
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
Crashlytics Gradle plugin (com.google.firebase.crashlytics) uploads mapping.txt automatically when mappingFileUploadEnabled = true.
For Sentry:
sentry {
autoUploadProguardMapping.set(true)
includeProguardMapping.set(true)
}
For Datadog:
datadog {
checkProjectDependencies = "warn"
}
and run ./gradlew app:uploadMappingRelease in CI.
Always keep the mapping file as a build artifact for 6+ months. The mapping is the only key to reading back obfuscated stack traces.
4. Android -- Native Symbols (NDK)
- Configure
android.buildTypes.release.ndk { debugSymbolLevel = "FULL" }.
- Crashlytics Gradle plugin uploads via
nativeSymbolUploadEnabled = true.
- Sentry:
sentry { uploadNativeSymbols.set(true) }.
- For manual vendors, upload the
build/intermediates/merged_native_libs/release/out/lib/<abi>/*.so unstripped copies.
5. Flutter -- Dart Symbols
Build release with split debug info:
flutter build apk --release --obfuscate --split-debug-info=build/symbols
flutter build ios --release --obfuscate --split-debug-info=build/symbols
Upload:
sentry-cli debug-files upload --org my-org --project my-app-flutter build/symbols
firebase crashlytics:symbols:upload --app <app-id> build/symbols
Without --split-debug-info Dart frames in release will be addresses, not function names.
6. React Native -- Source Maps and Hermes
For Hermes (default since RN 0.70):
react-native bundle --platform ios --dev false --entry-file index.js \
--bundle-output ios-release.bundle \
--sourcemap-output ios-release.bundle.map
node_modules/react-native/sdks/hermesc/osx-bin/hermesc \
-emit-binary -output-source-map ios-release.bundle \
-out ios-release.hbc
sentry-cli sourcemaps upload --release "my-app@${VERSION}+${BUILD}" \
--dist ios ios-release.bundle ios-release.bundle.map
Repeat for Android with android-release.bundle + .map. Use @sentry/react-native CLI helpers (sentry-expo upload-sourcemaps) where applicable.
7. CI Integration
A sample GitHub Actions step that fails the release build if symbols cannot be uploaded:
- name: Upload symbols
if: github.ref_type == 'tag'
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
set -euo pipefail
sentry-cli debug-files upload --org my-org --project my-app-android \
app/build/outputs/mapping/release/mapping.txt \
app/build/intermediates/merged_native_libs/release/out/lib
sentry-cli sourcemaps upload \
--release "my-app@${VERSION}+${BUILD}" --dist android \
android-release.bundle android-release.bundle.map
Store a checksum of each uploaded symbol in a release manifest so you can later prove which symbols exist for which build id.
8. Verification
Before promoting a release:
- Trigger a forced crash in a release build on a staging device.
- Confirm the resulting issue shows function names and line numbers in Crashlytics/Sentry/Datadog.
- Confirm native frames (if any) are resolved, not
??? or raw hex addresses.
- Confirm the issue is associated with the correct
release identifier.
If any step fails, block the release until symbols are fixed.
Checklist