Guided React Native and Expo setup
Configure the required Expo plugin before rebuilding, then optionally enable release source maps.
This guide targets published @crumbsdk/react-native@0.0.1-rc.6. Guided setup
first shipped in rc.5 and is absent from rc.4. Use the exact version in the
quickstart and rebuild the native app after installation.
Install, configure, verify
Install @crumbsdk/react-native and the compatible react-native-nitro-modules
peer using your existing package manager. The SDK now includes its pinned
source-map uploader; do not install a second Crumb package manually. Build
commands require Node.js 22 or newer. The uploader is not imported into the app.
After installation, run the package's crumb setup command from the app folder:
npx --no-install crumb setupThe command detects Expo or bare React Native, asks whether to configure source maps, previews which files it will edit, and requests confirmation. It preserves existing plugins and wraps supported Metro/Xcode/Gradle configuration. Run it again safely after an interrupted setup. Review the resulting version-control diff before committing.
Required for Expo: register the config plugin
Complete this step before prebuild, even when source maps are disabled.
crumb.config.json controls optional source-map uploads; it does not register
native dependencies. Keep @crumbsdk/react-native once in the active Expo
plugins array so prebuild can register the bundled iOS pods.
- Only
app.json: apply the changes previewed bycrumb setup, then confirmexpo.pluginscontains"@crumbsdk/react-native". Keep your other plugin entries. app.config.ts,app.config.jsor another dynamic config: add the plugin manually to the returned config'spluginsarray. Setup preserves dynamic config and prints this manual step; creatingcrumb.config.jsondoes not complete it. An entry inapp.jsoncan be lost if the dynamic config replaces its plugins.
Conditional configs and app variants
Keep your current selection logic and add the plugin after selecting a config.
For example, where previewConfig and productionConfig are your existing Expo
config objects with top-level plugins:
const selectedConfig =
process.env.EXPO_PUBLIC_APP_VARIANT === "preview"
? previewConfig
: productionConfig;
export default {
...selectedConfig,
plugins: [...(selectedConfig.plugins ?? []), "@crumbsdk/react-native"],
};Keep the selected config's existing plugins. Add Crumb once, either in this
shared return value or in every variant you intend to build. If your selected
object is wrapped in expo: { ... }, merge into that object's expo.plugins
instead; Expo ignores sibling fields outside the wrapper. The environment
variable and variant names above are examples; use your app's existing selection.
Verify the selected config, then rebuild
Run this with the same environment variables you use for prebuild or EAS. It prints only plugin names and exits unsuccessfully if Crumb is missing:
npx expo config --type prebuild --json | node -e "
const config = JSON.parse(require('node:fs').readFileSync(0, 'utf8'));
const names = (config.plugins ?? []).map(p => Array.isArray(p) ? p[0] : p);
console.log(names);
if (!names.includes('@crumbsdk/react-native')) process.exit(1);
"Repeat for every variant you intend to build. The output must include
@crumbsdk/react-native. A successful crumb doctor check alone does not verify
that a dynamic config includes the plugin.
Rebuild the native development client after changing plugins. For local iOS:
npx expo prebuild --platform ios
npx expo run:iosFor Android, use --platform android and npx expo run:android, or your existing
EAS development-build workflow. Expo Go and restarting Metro cannot apply native
plugin changes. Continue with app initialization only after native setup.
Optional source-map build hooks
For a reproducible noninteractive setup, copy the upload origin from your project's integration instructions:
npx --no-install crumb setup --source-maps --upload-url https://upload.example.invalid --applyThe origin above is synthetic. Omitting --apply outside an interactive terminal
only previews the changes. Run from an installed project so npx resolves the
local Crumb executable.
Dynamic Expo config and ESM/TypeScript Metro files receive precise manual instructions instead of automatic rewrites. Kotlin Gradle scripts and ambiguous Xcode projects also require the manual hooks below. Setup does not guess which of several app projects to modify or insert initialization into an unknown entry point.
Add your dashboard's normal Crumb.start configuration. If you chose JavaScript
crash reporting, explicitly enable diagnostics.javascriptCrashCapture.enabled.
Remove manual release.bundleVersion: a release build now supplies the identity
for its actual JavaScript. Native app version/build default to the installed
binary and are frozen when a JavaScript failure occurs. A conflicting manual
bundle identity is rejected by Crumb.start; custom pipelines can omit the
automatic hooks and continue supplying the full explicit release configuration.
Ordinary bug reports work without source-map setup or an upload credential.
Build secret and upload behavior
Create a dedicated source-map upload credential for the project and set
CRUMB_SOURCE_MAP_TOKEN in the environment running release builds. For EAS,
configure it as a build secret. Never place it in crumb.config.json, an app
initialization snippet, an EXPO_PUBLIC_* variable, or a command-line argument.
The checked-in configuration contains only:
{
"version": 1,
"sourceMaps": {
"enabled": true,
"uploadUrl": "https://upload.example.invalid",
"strict": false
}
}Native release hooks generate an immutable bundle identity before Metro runs. Metro includes a small non-secret polyfill before app startup. The uploader uses the final native bundle and composed Hermes source map, verifies that the map contains that build identity, and calls the existing v1 upload contract. Debug builds do not upload. The Expo plugin installs the same hooks during prebuild; no separate EAS hook script is needed for native release builds.
Upload failures warn by default and preserve the native build. Set
CRUMB_SOURCE_MAP_STRICT=1, or sourceMaps.strict: true, to fail the release job
if artifacts cannot be verified. Set CRUMB_SOURCE_MAP_DRY_RUN=1 for local
artifact validation without reading the upload credential or sending files;
this is not evidence of a successful hosted upload.
Each upload retains a copy of its exact bundle, map, release metadata, and retry receipt under the build's Crumb directory. Keep these private and archive them with release artifacts if your build workspace is disposable. Failed uploads print the receipt location:
npx --no-install crumb retry --receipt /path/to/retained/upload-ios-1.0.0-42.jsonRetry checks byte identity and does not rebuild. If retained bytes have changed, restore the original files. The configured upload origin must still identify the same project/environment. The upload-only credential cannot read reports.
Local and dashboard verification
npx --no-install crumb doctorDoctor checks installed dependencies, the Metro wrapper, and whether an upload credential exists in this environment. It does not claim that a credential is valid, that native hooks have executed, that files reached Crumb, or that a crash was decoded. After a release build, send a synthetic fatal JavaScript crash, relaunch the app, and confirm the dashboard shows its expected original source file and line. Missing or mismatched maps must retain the raw crash explicitly.
EAS Update
Keep the supported native release targets explicit: a runtime version or update channel is not a unique native build. The upload contract requires one entry for every eligible platform/version/build tuple. For example:
[
{ "platform": "ios", "appVersion": "1.0.0", "nativeBuild": "42" },
{ "platform": "android", "appVersion": "1.0.0", "nativeBuild": "42" }
]Save this as crumb-targets.json, then create and upload a new export:
npx --no-install crumb export --output dist/next-update --targets crumb-targets.jsonThe helper uses the app's installed Expo CLI, injects the export's own identity,
requests final source maps, and selects bundle files from Expo's export metadata.
All requested target artifacts are checked before uploads. An existing output
directory is never overwritten. The command does not publish an OTA update.
Use --dry-run to validate locally without uploading.
Publish that exact export with the project's pinned EAS CLI and its normal
environment/channel options, using --input-dir dist/next-update --skip-bundler.
Do not rebuild after upload. Embedded bundles and older/rolled-back updates each
carry their own identity. .crumb/ holds local export metadata and is ignored by
setup; exported maps/bundles must also remain outside public source control.
Automatic discovery of all eligible native builds and arbitrary custom export formats are not supported. Use the advanced uploader for a pipeline outside this supported path.
Manual hooks for custom project layouts
Wrap the existing Metro configuration with withCrumb from
@crumbsdk/react-native/metro, preserving its other options. For CommonJS:
const { withCrumb } = require("@crumbsdk/react-native/metro");
module.exports = withCrumb(existingConfig);Expo apps must keep @crumbsdk/react-native in their config plugins. Rebuild the
native projects after configuration changes.
Bare Android applications apply the package's tools/crumb.gradle from their app
module after the React Native plugin. The hook connects to the actual bundle
tasks and reads final variant output versions; it does not assume a release/
artifact path. Kotlin DSL can apply the same Groovy script via apply(from = ...).
Bare iOS applications keep the bundled-native Podfile helper and wrap their
existing bundle phase with tools/ios.cjs, passing the original shell command as
arguments. The wrapper preserves that phase's exit code and uses its Xcode build
settings and processed plist. Use setup's generated phase as the reference; never
replace a custom bundle command with a generic rebuild command.