Skip to main content
A release build of a React Native app runs its JavaScript as Hermes bytecode: a crash’s stack reads crashCheckout (address at main.jsbundle:1:454705). Apsio turns that into crashCheckout in src/App.tsx:100 with the bundle’s source map. See React Native for how the map is matched.

Which map

Upload the map React Native composes from Metro’s and Hermes’ maps, one per bundle and build: On iOS, set SOURCEMAP_FILE in that build phase, for example export SOURCEMAP_FILE="$DERIVED_FILE_DIR/main.jsbundle.map"; without it no map is written.

Upload it after the build

  • --build-id is what the app reports as app.build_id: on Android the build id the Gradle plugin writes into the manifest (see Give the build an id), on iOS the main binary’s UUID, the same UUID as its dSYM (dwarfdump --uuid on the app’s binary). Another string works too, such as the SDK’s <package>@<version>+<code> fallback, but two builds of one version then share it; prefer the build id.
  • --bundle is the bundle’s file name in the app. By default it is the map’s file name without .map: index.android.bundle.map uploads for index.android.bundle.
  • --bundle-id is for an over-the-air bundle (EAS Update, CodePush): the id your app passes to the SDK (start({ jsBundleId })). Crashes from that bundle use only its map, never the build’s: upload the map of every update you publish.
  • The map’s sourcesContent is removed before upload, so your source code never leaves the machine; Apsio shows functions, files and lines, not the code around them.
  • Maps are limited to 64 MB and 8 million mappings; larger ones are refused.
  • Apsio keeps one map per build and bundle: the same map again is reported as present, a different one is refused (exit 4) unless --force. --dry-run checks the map without a token, and --json prints a report. Like every upload, it goes to the symbol upload URL (APSIO_SYMBOLS_URL, https://symbols.apsio.io by default; see the CLI).
Crashes and errors that arrived before their map are symbolicated when it lands, and grouped again with your function names.

What stays unsymbolicated

  • Frames in other Hermes segments than the first (bundles split into segments): v0 resolves segment 1, the whole bundle in a plain build. Frames in other segments keep their function name and offset even with the map uploaded, and the crash counts as not fully symbolicated.
  • A map from another build of the bundle: bytecode offsets change with every build.
  • Native frames ((native)) and Hermes’ own code, which have no source.