Flare by Spatie
    • Error Tracking
    • Performance Monitoring
    • Logs
  • Pricing
  • Docs
  • Insights
  • Changelog
  • Back to Flare ⌘↵ Shortcut: Command or Control Enter
  • Sign in
  • Try Flare for free
  • Error Tracking
  • Performance Monitoring
  • Logs
  • Pricing
  • Docs
  • Insights
  • Changelog
    • Back to Flare ⌘↵ Shortcut: Command or Control Enter
    • Try Flare for free
    • Sign in
Flare Flare Laravel Laravel PHP PHP JavaScript JavaScript React React Vue Vue Svelte Svelte Protocol Protocol
  • General
  • Installation
  • Resolving bundled code
  • API reference
  • Electron
  • Installation
  • React Native
  • Installation
  • Resolving bundled code
  • Errors
  • Error boundary
  • Error handler
  • Reporting errors
  • Client hooks
  • Data Collection
  • Adding custom context
  • Adding glows
  • Identifying users

Resolving bundled code

When React Native builds your app for release, Metro minifies your JavaScript, and the default engine (Hermes) compiles it further to bytecode. By the time an error is reported, the stack frames point at a single minified line in index.android.bundle or main.jsbundle — not at your original source. A frame ends up looking like this:

address at index.android.bundle:1:428913

Flare can turn that back into the real file, line, and code snippet using a sourcemap. The @flareapp/react-native-sourcemaps package uploads the sourcemap your release build produces and ties it to your errors.

This is only needed for release builds. In development your app runs the unminified bundle from the Metro dev server, so stack traces are already readable and there is nothing to upload.

How it works

There is no native module and no Metro bundler plugin involved. The package has two halves that share a single version string:

  1. A Babel plugin stamps a version into your app bundle at build time, so every error your app reports carries that version.
  2. A CLI (flare-rn-sourcemaps upload) uploads your sourcemap to Flare under the same version.

When an error arrives, Flare looks up the sourcemap whose version matches the report, and uses it to resolve the frames. The whole job is to make sure the version baked into your app and the version you upload the sourcemap under are identical — which the package handles for you as long as you set it in one place.

Installing the package

Install it as a dev dependency — it only runs at build time and is never bundled into your app:

npm install --save-dev @flareapp/react-native-sourcemaps
# or
yarn add --dev @flareapp/react-native-sourcemaps
# or
pnpm add --save-dev @flareapp/react-native-sourcemaps

This assumes you already have the React Native SDK installed.

Step 1: Add the Babel plugin

Add the plugin to your babel.config.js:

module.exports = {
    presets: ['module:@react-native/babel-preset'],
    plugins: ['@flareapp/react-native-sourcemaps/babel'],
};

The only thing the plugin does, is it inlines the sourcemap version into your bundle at build time.

Step 2: Pass the version to Flare

Import flareSourcemapVersion and pass it to flare.configure() where you set up Flare (see Installation):

// App.tsx
import { flare } from '@flareapp/react-native';
import { flareSourcemapVersion } from '@flareapp/react-native-sourcemaps/runtime';

flare.light('YOUR PROJECT KEY').configure({ sourcemapVersionId: flareSourcemapVersion });

flareSourcemapVersion is a normal typed string, so there is nothing to add to your tsconfig.json and no @types/node to install. At build time the Babel plugin replaces it with the actual version and removes the import. If you build without the plugin, it is an empty string and Flare simply skips sourcemap resolution.

Unlike the web plugins (Vite, Webpack), this package does not pass your project key to the client. You still call flare.light('YOUR PROJECT KEY') yourself.

Step 3: Choose a version

The version is read from the FLARE_SOURCEMAP_VERSION environment variable. Set it in the shell that runs your release build, to anything project-unique — a Git commit SHA is a good choice:

export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"

Both the Babel plugin (step 1) and the upload CLI (step 4) read this same variable, so they always agree. If you leave it unset it falls back to your app's package.json version, with a warning — fine for a quick test, but prefer setting it explicitly so a rebuild that does not change the app version still gets a fresh version.

Step 4: Build and upload the sourcemap

Your release build already generates a sourcemap. With Hermes, it is the composed sourcemap (the Metro sourcemap merged with the Hermes bytecode sourcemap) — that is the one to upload. Build with FLARE_SOURCEMAP_VERSION set, then point the CLI at the generated .map file under the same version.

Prefer not to upload by hand? You can have the upload run automatically as part of your release build, so it happens on every release without a separate command — through native build hooks in a bare project, or an Expo config plugin in a managed (CNG) one. See Automatic upload for bare React Native and Automatic upload for Expo (CNG) below.

Android

A release build writes the composed sourcemap into the Gradle build output:

export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"

# Build the release app (this also produces the sourcemap)
npx react-native run-android --mode release
# or: cd android && ./gradlew assembleRelease

# Upload it
npx flare-rn-sourcemaps upload \
  --api-key "YOUR PROJECT KEY" \
  --sourcemap android/app/build/generated/sourcemaps/react/release/index.android.bundle.map \
  --bundle-filename index.android.bundle \
  --version "$FLARE_SOURCEMAP_VERSION"

iOS

Tell the iOS build where to write the sourcemap by setting SOURCEMAP_FILE (React Native's build script reads it), then upload that file:

export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"
export SOURCEMAP_FILE="$(pwd)/main.jsbundle.map"

# Build the release app in Xcode (Product > Archive) or:
npx react-native run-ios --mode Release

npx flare-rn-sourcemaps upload \
  --api-key "YOUR PROJECT KEY" \
  --sourcemap main.jsbundle.map \
  --bundle-filename main.jsbundle \
  --version "$FLARE_SOURCEMAP_VERSION"

Expo

If you use Expo, export your bundle and sourcemaps, then upload the generated map:

FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)" npx expo export

npx flare-rn-sourcemaps upload \
  --api-key "YOUR PROJECT KEY" \
  --sourcemap dist/_expo/static/js/ios/<your-bundle>.hbc.map \
  --bundle-filename main.jsbundle \
  --version "$FLARE_SOURCEMAP_VERSION"

On success you will see:

@flareapp/react-native-sourcemaps: Uploading sourcemap "index.android.bundle" (version a1b2c3d) to Flare.
@flareapp/react-native-sourcemaps: Successfully uploaded sourcemap to Flare.

Automatic upload for bare React Native

Step 4 uploads the sourcemap by hand. For a bare React Native project you can instead wire the upload into your native release build, so it happens automatically every time you build a release — no separate command to remember, and no way to forget it in CI.

This covers bare React Native (a project with its own android/ and ios/ directories). For Expo managed (CNG) projects, see Automatic upload for Expo (CNG) below.

You still need the Babel plugin (step 1), the flareSourcemapVersion wiring (step 2), and FLARE_SOURCEMAP_VERSION set in your build environment (step 3). The automatic hooks replace only the upload in step 4.

Configure your key

The native hooks need your project's API key. There are two ways to pass the key, depending on whether you are fine having the key in your repository or not.

Option 1 — flare.json (simplest). If you don't mind committing the key, create a flare.json file at the root of your project (next to package.json):

{
    "apiKey": "YOUR PROJECT KEY"
}

The key is now in version control along with the rest of your config. That's fine for many teams, and there's nothing else to set up.

Option 2 — FLARE_API_KEY environment variable (keeps the key out of git). If you'd rather not commit the key, set FLARE_API_KEY in the environment that runs the build. Most teams will have CI setup for their release builds, so they have it as a secret in their CI/EAS build environment (build.<profile>.env in eas.json, repository secrets in GitHub Actions, etc.).

To test the upload locally — which is the rare case, since you don't normally create a production build on your laptop — drop the key in a gitignored .env.local (or .env) next to package.json. The hooks will auto-load FLARE_API_KEY from there at build time, so a local release build picks it up without exporting anything by hand:

# .env.local (gitignored)
FLARE_API_KEY=YOUR PROJECT KEY

If both options are present, the environment wins. The full precedence is: shell env > .env.local > .env > flare.json.

In the automatic flow the version comes only from FLARE_SOURCEMAP_VERSION (the same variable the Babel plugin reads), so the version baked into your app and the version on the sourcemap always match. We will not fallback to the package.json version: if FLARE_SOURCEMAP_VERSION is unset, the upload is skipped with a warning log in your build output, rather than us guessing a version that would not resolve.

Android

Add this line to your android/app/build.gradle. It can go anywhere in the file — the script hooks the build lazily, so the order doesn't matter:

apply from: "../../node_modules/@flareapp/react-native-sourcemaps/flare.gradle"

That is all. After each release JS bundle is built, the composed Hermes sourcemap is uploaded automatically.

iOS

First, tell React Native's bundle step to emit the composed sourcemap by adding this line to ios/.xcode.env:

export SOURCEMAP_FILE="$TARGET_TEMP_DIR/main.jsbundle.map"

Use $TARGET_TEMP_DIR, not $CONFIGURATION_BUILD_DIR. With Hermes, react-native-xcode.sh writes an intermediate sourcemap to $CONFIGURATION_BUILD_DIR/main.jsbundle.map and deletes it after composing the final map, so pointing SOURCEMAP_FILE there would have the composed map deleted before it can be uploaded.

Then, in Xcode, add a new Run Script build phase (Build Phases > + > New Run Script Phase), placed after the existing "Bundle React Native code and images" phase. Name it "Upload Flare sourcemaps" and paste:

set -e
WITH_ENVIRONMENT="../node_modules/react-native/scripts/xcode/with-environment.sh"
FLARE_XCODE="../node_modules/@flareapp/react-native-sourcemaps/scripts/flare-xcode.sh"
/bin/sh "$WITH_ENVIRONMENT" "$FLARE_XCODE"

The with-environment.sh wrapper is the same one React Native uses for its own bundle phase; it makes sure the Flare phase sees SOURCEMAP_FILE (and any FLARE_* variables) from your .xcode.env.

In that build phase, uncheck "Based on Dependency Analysis". The script has no input or output files, so otherwise Xcode warns that it has ambiguous dependencies and runs on every build. You want it to run on every release build anyway. (The Expo config plugin sets this for you.)

The key comes from flare.json or the auto-loaded .env.local, so it's there even for a GUI build. The version (FLARE_SOURCEMAP_VERSION) is the one variable the phase still reads from the build's environment, inherited from whatever launched the build. A react-native run-ios, xcodebuild, or Fastlane run from a terminal that exported it works. A build started from the Xcode GUI (including Product > Archive) doesn't have it, so the upload skips with the banner — archive from the command line or in CI for releases. The build still succeeds either way.

Custom build configurations (bare / brownfield)

Your build configuration doesn't have to be called Release.

The hooks upload whenever a build makes a JavaScript bundle. They skip a build only when its name contains debug (any casing).

  • Uploads: Release, Staging, Production, AppStore, your own Android build type
  • Skipped: Debug, debug, StagingDebug

A debug build runs from Metro and makes no bundle, so there's nothing to upload. The hook skips it and your dev builds stay fast.

Want a bundling config to skip the upload anyway? Put debug in its name. Or remove the Flare setup you added above — the apply from "…/flare.gradle" line in android/app/build.gradle on Android, or the Upload Flare sourcemaps build phase in Xcode on iOS — which turns the automatic upload off completely.

Builds never fail because of sourcemaps

A sourcemap problem never breaks your build: if the key or FLARE_SOURCEMAP_VERSION is missing, or the upload itself fails, the build still succeeds and a clear, boxed warning is printed with a command you can run to upload the map by hand.

Automatic upload for Expo (CNG)

For an Expo project that uses prebuild (managed / CNG), a config plugin wires the upload into the generated native projects on every expo prebuild, so it survives regeneration. You pass the plugin your key the same two ways as the bare project — pick one depending on whether you mind the key living in your repository.

Option 1 — inline in app.json (simplest). If you don't mind committing the key, add the plugin with the key inline:

{
    "expo": {
        "plugins": [
            ["@flareapp/react-native-sourcemaps/expo", { "apiKey": "YOUR PROJECT KEY" }]
        ]
    }
}

The key is now in version control along with the rest of your Expo config.

Option 2 — app.config.ts + environment variable (keeps the key out of git). If you'd rather not commit the key, switch from the static app.json to a dynamic app.config.ts (or app.config.js) and read the key from the environment. The Expo CLI auto-loads .env / .env.local, so a local build picks the key up without exporting anything by hand, and in CI/EAS you set it as a secret (build.<profile>.env in eas.json):

// app.config.ts
import type { ExpoConfig } from 'expo/config';

// Resolved by the Expo CLI, which auto-loads .env / .env.local — so the Flare key
// never lives in a committed file. The same key feeds two consumers:
//   - the sourcemaps config plugin -> build-time flare.json (sourcemap upload)
//   - `extra` -> read at runtime via expo-constants to boot the SDK
// The report ingest URL is independent of the plugin's sourcemap-upload endpoint.
const flareApiKey = process.env.FLARE_API_KEY ?? '';
const flareIngestUrl = process.env.FLARE_INGEST_URL ?? 'https://ingress.flareapp.io/v1/errors';

const config: ExpoConfig = {
    name: 'your-app',
    slug: 'your-app',
    // ...your existing icon, ios, android, web config
    plugins: [['@flareapp/react-native-sourcemaps/expo', { apiKey: flareApiKey }]],
    extra: {
        flareApiKey,
        flareIngestUrl,
    },
};

export default config;

Reading the key from extra at runtime (via expo-constants) lets you keep it out of your source there too, instead of hardcoding it in flare.light('YOUR PROJECT KEY'). If you only care about the sourcemap upload, the plugins line alone is enough — the extra block is optional.

The plugin only does the native wiring. You still:

  • add the Babel plugin (step 1) and pass flareSourcemapVersion to flare.configure() (step 2), and
  • set FLARE_SOURCEMAP_VERSION in the build environment (step 3): a shell export locally, or eas.json's "build": { "<profile>": { "env": { "FLARE_SOURCEMAP_VERSION": "..." } } } for EAS Build.

It runs only for release native builds. The upload happens inside the native build. It needs FLARE_SOURCEMAP_VERSION in that build's environment, plus a key — from the apiKey you passed the plugin (written into flare.json) or a gitignored .env.local the hooks auto-load. If the version or key is missing, the upload is skipped with a warning and the build still succeeds.

Building for distribution

You don't have to use EAS. The build phase inherits the environment of whatever launches the build, so any release build started from a shell with the variables set will upload:

  • EAS Build — set them in eas.json (build.<profile>.env).
  • eas build --local — the same eas.json, run on your machine.
  • A command-line archive — xcodebuild archive or Fastlane on iOS, ./gradlew bundleRelease (the App Store .aab) on Android — run from a shell where you exported the variables:
export FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)"
# then your normal release build, e.g.
xcodebuild -workspace ios/YourApp.xcworkspace -scheme YourApp -configuration Release archive   # iOS
./gradlew :app:assembleRelease                                                                  # Android

OTA updates (EAS Update) are not covered. The plugin's hooks fire only during a native build. eas update ships a new JS bundle via expo export with no Gradle or Xcode phase, so no sourcemap is uploaded and those frames stay minified. For an OTA release, upload the map yourself:

FLARE_SOURCEMAP_VERSION="$(git rev-parse --short HEAD)" npx expo export
npx flare-rn-sourcemaps upload \
  --api-key "YOUR PROJECT KEY" \
  --sourcemap dist/_expo/static/js/ios/<your-bundle>.hbc.map \
  --bundle-filename main.jsbundle \
  --version "$FLARE_SOURCEMAP_VERSION"

Use the same FLARE_SOURCEMAP_VERSION for the export and the upload, or the map will not match.

CLI options

npx flare-rn-sourcemaps upload [options]
Option Description
--sourcemap (required) Path to the composed .map file to upload.
--api-key (required) Your Flare project's public API key. Falls back to the FLARE_API_KEY environment variable.
--bundle-filename The name the bundle appears under in stack frames. Defaults to the sourcemap's filename without .map (e.g. index.android.bundle).
--version The sourcemap version. Defaults to FLARE_SOURCEMAP_VERSION, then your package.json version.
--api-endpoint The upload endpoint. Defaults to https://flareapp.io/api/sourcemaps.

Verifying it worked

Build a release app with the steps above and upload the sourcemap. Then send a test report from somewhere in your app — for example a button that calls flare.test():

import { flare } from '@flareapp/react-native';

// e.g. in a button's onPress
flare.test();

Open that report in Flare. The stack frames should show your real files (App.tsx, your components) with code snippets, instead of a minified index.android.bundle:1 (or main.jsbundle:1) line.

If a frame is still unresolved, the cause is almost always one of:

  • The version does not match. The app reported one version and the sourcemap was uploaded under another. Make sure FLARE_SOURCEMAP_VERSION is set to the same value for both the build and the upload, and that you rebuilt the app after adding the Babel plugin (step 1).
  • The sourcemap was uploaded after the error. Flare resolves frames when the error is received, so upload the sourcemap as part of your release, before users hit errors. Re-uploading does not re-resolve errors that already came in.
  • The wrong sourcemap. With Hermes you must upload the composed map your release build produced, not the raw Metro packager map (*.packager.map).

Manually uploading sourcemaps

The CLI is a thin wrapper around Flare's sourcemap API. If you need to upload from a custom script, see Manually uploading sourcemaps. The version_id you send must equal the flareSourcemapVersion baked into your app, and relative_filename must match the bundle name in your stack frames (index.android.bundle on Android, main.jsbundle on iOS).

Installation Error boundary

On this page

  • How it works
  • Installing the package
  • Step 1: Add the Babel plugin
  • Step 2: Pass the version to Flare
  • Step 3: Choose a version
  • Step 4: Build and upload the sourcemap
  • Automatic upload for bare React Native
  • Automatic upload for Expo (CNG)
  • CLI options
  • Verifying it worked
  • Manually uploading sourcemaps

Catch errors and fix slowdowns with Flare, the full-stack application monitoring platform for Laravel, PHP & JavaScript.

  • Platform
  • Error Tracking
  • Performance Monitoring
  • Pricing
  • Support
  • Resources
  • Insights
  • Newsletter
  • Changelog
  • Documentation
  • Affiliate program
  • uptime status badge Service status
  • Terms of use
  • DPA
  • Privacy & cookie Policy
Made in by
Flare