@bugsplat/expo

中文文档
0.8.2发布于 2026/10/27MITTypeScriptCommonJS零依赖近 30 天下载 3419

BugSplat crash and error reporting for Expo apps across iOS, Android, and Web

原始使用文档
文档目录35 节
  1. Crash and error reporting built for busy developers.
  2. Introduction
  3. Installation
  4. Configuration
  5. Plugin Options
  6. Symbol Upload
  7. Usage
  8. Initialize
  9. Report Errors
  10. Set User Info
  11. Set Custom Attributes
  12. Test Crash
  13. Trigger a Native Hang (ANR)
  14. Error Boundary
  15. Collecting user input before posting
  16. Shake to Send Feedback
  17. User Feedback
  18. How It Works
  19. API
  20. init(database, application, version, options?)
  21. post(error, options?)
  22. postFeedback(title, options?)
  23. useFeedback()
  24. ErrorBoundary
  25. setUser(name, email)
  26. setAttribute(key, value)
  27. crash()
  28. hang()
  29. Expo Go
  30. Testing Native Crashes
  31. Testing Native Hangs (ANRs)
  32. Troubleshooting
  33. Android: Crashes not uploading on emulator
  34. iOS: No crash report after test crash
  35. License

bugsplat-github-banner-basic-outline

BugSplat

Crash and error reporting built for busy developers.

Follow BugSplat on Bluesky Join BugSplat on Discord
@bugsplat/expo on npm

Introduction

BugSplat's @bugsplat/expo package provides crash and error reporting for Expo apps across iOS, Android, and Web. BugSplat provides you with invaluable insight into the issues tripping up your users. Our Expo integration collects native crash reports, JavaScript errors, and custom metadata so that you can fix bugs and deliver a better user experience.

Installation

npx expo install @bugsplat/expo expo-blob expo-file-system @bugsplat/react

expo-blob, expo-file-system and @bugsplat/react are peer dependencies so that exactly one copy of each ends up in your app. SDK 55 through 58 are supported.

expo-blob ships native code that has to match your project's Expo SDK, so install the release line for your SDK — 58.x for SDK 58, 57.x for SDK 57, and so on (for example npm install expo-blob@~58.0.3). It is not yet listed in Expo's bundled native modules manifest, so expo install will not pin it for you.

Configuration

Add the config plugin to your app.json or app.config.js:

Credentials for symbol upload can be set via environment variables (BUGSPLAT_CLIENT_ID, BUGSPLAT_CLIENT_SECRET) or in the plugin config.

{
  "expo": {
    "plugins": [
      ["@bugsplat/expo", {
        "database": "<your-database>",
        "enableSymbolUpload": true
      }],
      ["expo-build-properties", {
        "android": {
          "minSdkVersion": 26
        }
      }]
    ]
  }
}

Replace <your-database> with your real BugSplat database name. The plugin rejects angle-bracket placeholders at expo prebuild so this exact snippet won't accidentally ship as a configuration.

The bugsplat-android SDK requires Android minSdk 26 (Android 8.0+). If your project's minSdk is already >= 26, the expo-build-properties plugin is not needed.

The plugin sets up required native permissions (Android) and optionally configures automatic symbol uploads for both platforms. Configure your database in code via init().

Plugin Options

OptionRequiredDescription
databaseYesBugSplat database name. Throws at expo prebuild if missing, empty, or set to a placeholder like "<your-database>". Becomes the source of truth — also used by the runtime init() call when read from app code.
enableSymbolUploadNoEnable automatic symbol upload for iOS (dSYMs + JS source maps) and Android (.so files + Hermes JS source maps).
symbolUploadClientIdWhen enableSymbolUpload: trueBugSplat API client ID (or set BUGSPLAT_CLIENT_ID env var)
symbolUploadClientSecretWhen enableSymbolUpload: trueBugSplat API client secret (or set BUGSPLAT_CLIENT_SECRET env var)

The plugin also validates expo.name, expo.version, and expo.android.package at prebuild — these flow into the symbol-upload scripts so the uploaded identity matches what your app reports at crash time.

Symbol Upload

Production crash reports require debug symbols to produce readable stack traces. When enableSymbolUpload is set, the config plugin automatically uploads symbols during iOS and Android release builds.

For manual uploads or CI/CD workflows, use @bugsplat/symbol-upload directly:

npm install --save-dev @bugsplat/symbol-upload
# Upload iOS dSYMs
npx @bugsplat/symbol-upload \
  -b your-database -a YourApp -v 1.0.0 \
  -i $BUGSPLAT_CLIENT_ID -s $BUGSPLAT_CLIENT_SECRET \
  -d /path/to/build/Products/Release-iphoneos \
  -f "**/*.dSYM"

# Upload Android .so files (converted to .sym)
npx @bugsplat/symbol-upload \
  -b your-database -a YourApp -v 1.0.0 \
  -i $BUGSPLAT_CLIENT_ID -s $BUGSPLAT_CLIENT_SECRET \
  -d android/app/build/intermediates/merged_native_libs \
  -f "**/*.so" -m

# Upload JavaScript source maps (after npx expo export --source-maps)
npx @bugsplat/symbol-upload \
  -b your-database -a YourApp -v 1.0.0 \
  -i $BUGSPLAT_CLIENT_ID -s $BUGSPLAT_CLIENT_SECRET \
  -d dist \
  -f "**/*.map"

Run npx @bugsplat/symbol-upload --help for all options.

Usage

Initialize

import { init } from '@bugsplat/expo';

await init('your-database', 'YourApp', '1.0.0', {
  userName: '[email protected]',
  userEmail: '[email protected]',
  appKey: 'optional-key',
});

Report Errors

import { post } from '@bugsplat/expo';

try {
  riskyOperation();
} catch (error) {
  const result = await post(error);
  console.log(result.success ? 'Reported!' : result.error);
}

Set User Info

import { setUser } from '@bugsplat/expo';

setUser('Jane Doe', '[email protected]');

Set Custom Attributes

import { setAttribute } from '@bugsplat/expo';

setAttribute('environment', 'production');

Test Crash

import { crash } from '@bugsplat/expo';

// Triggers a native crash (iOS/Android) or throws an error (web)
crash();

Trigger a Native Hang (ANR)

import { hang } from '@bugsplat/expo';

// Freezes the UI thread in a native loop. On Android the system ANR detector
// fires after ~5s of unresponsive input and BugSplat captures a dump with
// symbolicated native frames. On iOS the bundled BugSplat-Apple hang tracker
// persists a fatal-hang report after the main thread stays unresponsive past
// its threshold (~2s by default); the report uploads on the next app launch.
hang();

Error Boundary

Wrap your component tree in <ErrorBoundary> to catch React render errors and report them to BugSplat automatically. Works identically on iOS, Android, and Web.

import { ErrorBoundary } from '@bugsplat/expo';

function App() {
  return (
    <ErrorBoundary fallback={<Text>Something went wrong</Text>}>
      <MyComponent />
    </ErrorBoundary>
  );
}

The fallback prop accepts a React node or a render function:

<ErrorBoundary
  fallback={({ error, resetErrorBoundary }) => (
    <View>
      <Text>{error.message}</Text>
      <Button title="Try again" onPress={resetErrorBoundary} />
    </View>
  )}
>
  <MyComponent />
</ErrorBoundary>

Collecting user input before posting

By default, <ErrorBoundary> posts to BugSplat the moment it catches an error. If you'd rather give the user a chance to describe what they were doing first — and bundle that into a single report instead of two — set disablePost on the boundary and post manually from your fallback:

<ErrorBoundary> is a class component, so hooks can't live directly inside its fallback render prop — extract the fallback into its own functional component:

import { useRef, useState } from 'react';
import { ErrorBoundary, post, type FallbackProps } from '@bugsplat/expo';
import { Text, TextInput, Button, View } from 'react-native';

function ErrorFallback({ error, componentStack, resetErrorBoundary }: FallbackProps) {
  const [description, setDescription] = useState('');
  const posted = useRef(false);

  const submit = async () => {
    if (posted.current) return;
    posted.current = true;
    await post(error, {
      description,
      attributes: { route: 'tasks/123' },
      attachments: componentStack
        ? [{
            filename: 'componentStack.txt',
            data: new Blob([componentStack], { type: 'text/plain' }),
          }]
        : undefined,
    });
  };

  return (
    <View>
      <Text>Something went wrong: {error.message}</Text>
      <TextInput value={description} onChangeText={setDescription} />
      <Button title="Submit" onPress={submit} />
      <Button title="Dismiss" onPress={() => { submit(); resetErrorBoundary(); }} />
    </View>
  );
}

<ErrorBoundary disablePost fallback={(props) => <ErrorFallback {...props} />}>
  <App />
</ErrorBoundary>

A few notes on this pattern:

  • post() is not idempotent. The useRef guard is the consumer's responsibility — without it, a fast double-tap (or "Submit then Dismiss") would fire two reports. useRef updates synchronously, so it guards taps that land in the same render window; useState would not.
  • componentStack is wrapped in a Blob. On native, attachments can also be a Uint8Array or a { uri, type } file reference (e.g. an expo-image-picker asset); @bugsplat/expo reads file references from disk with expo-file-system when the report is posted.
  • attributes becomes a queryable column in the BugSplat dashboard — useful for filtering crashes by route, feature flag, build channel, etc.
  • If posting fails and you want retry, check the success property of the value returned by post() and reset posted.current accordingly. The recipe doesn't show this to keep it minimal.

Shake to Send Feedback

A common pattern: let users shake the device to open a feedback dialog. @bugsplat/expo doesn't bundle a shake detector (to keep the package narrow), but wiring one up takes ~30 lines using expo-sensors.

npx expo install expo-sensors
import { useEffect, useRef, useState } from 'react';
import { Accelerometer } from 'expo-sensors';
import { postFeedback } from '@bugsplat/expo';

function useShake(onShake: () => void, enabled = true) {
  const lastFire = useRef(0);
  const cb = useRef(onShake);
  cb.current = onShake;
  useEffect(() => {
    if (!enabled) return;
    Accelerometer.setUpdateInterval(100);
    const sub = Accelerometer.addListener(({ x, y, z }) => {
      // values are in g-units; magnitude ≈ 1 at rest (gravity)
      if (Math.abs(Math.sqrt(x * x + y * y + z * z) - 1) < 1.2) return;
      const now = Date.now();
      if (now - lastFire.current < 1500) return; // 1.5s cooldown
      lastFire.current = now;
      cb.current();
    });
    return () => sub.remove();
  }, [enabled]);
}

function App() {
  const [open, setOpen] = useState(false);
  // Auto-disabled while the modal is open so the user's input shake
  // can't bounce them right back into it.
  useShake(() => setOpen(true), !open);
  return /* ... feedback modal that calls postFeedback() on submit ... */;
}

Testing: real iOS/Android devices work out of the box. The Android emulator can simulate shake via Extended Controls → Virtual sensors. The iOS Simulator's Device → Shake menu (Ctrl+⌘+Z) does not fire accelerometer data — it sends UIEventSubtypeMotionShake, a different channel. If iOS Simulator testability matters to you, swap expo-sensors for react-native-shake, which listens to both channels (tradeoff: it's a third-party native module and will crash the app on startup if you forget to run npx expo prebuild --clean after install).

User Feedback

Submit user feedback tied to your BugSplat database. Works on iOS, Android, and Web.

Imperative API — call from anywhere after init():

import { postFeedback } from '@bugsplat/expo';

const result = await postFeedback('Login button broken', {
  description: 'Nothing happens when I tap sign in',
});
console.log(result.success ? `Feedback #${result.crashId} posted` : result.error);

React hook — useful for driving a feedback form with built-in loading/error state:

import { useFeedback } from '@bugsplat/expo';

function FeedbackForm() {
  const { postFeedback, loading, error } = useFeedback();

  return (
    <Button
      title={loading ? 'Sending…' : 'Send feedback'}
      disabled={loading}
      onPress={() =>
        postFeedback('Login button broken', {
          description: 'Nothing happens when I tap sign in',
        })
      }
    />
  );
}

How It Works

PlatformNative CrashesJS Error Reporting
iOSbugsplat-apple (PLCrashReporter)HTTP POST to /post/js/
Androidbugsplat-android (Crashpad)HTTP POST to /post/js/
WebN/Abugsplat-js

API

init(database, application, version, options?)

Initialize BugSplat crash reporting. Must be called before other functions.

Options:

  • appKey?: string - Queryable metadata key
  • userName?: string - User name for reports
  • userEmail?: string - User email for reports
  • autoSubmitCrashReport?: boolean - Auto-submit crashes (iOS only, default: true)
  • attributes?: Record<string, string> - Custom key-value attributes
  • attachments?: string[] - File paths to attach (native only)
  • description?: string - Default description

post(error, options?)

Manually report an error. Returns { success: boolean, error?: string }.

postFeedback(title, options?)

Submit user feedback. title is a short summary (required); options.description holds the longer body. Returns { success: boolean, error?: string, crashId?: number }.

useFeedback()

React hook returning { postFeedback, loading, response, error } for driving feedback forms.

ErrorBoundary

React error boundary that reports render errors to BugSplat automatically. Accepts a fallback (ReactNode or render function receiving { error, componentStack, response, resetErrorBoundary }).

setUser(name, email)

Update user info for subsequent reports.

setAttribute(key, value)

Set a custom attribute. Note: not supported on web.

crash()

Trigger a test crash to verify integration.

hang()

Freeze the UI thread in a native loop. On Android this produces a real ANR with symbolicated native frames; on iOS the BugSplat-Apple hang tracker persists a fatal-hang report after the unresponsiveness threshold (~2s) and uploads it on next launch. Requires a development build — no-op in Expo Go.

Expo Go

@bugsplat/expo works in Expo Go with reduced functionality. Since native modules are not available in Expo Go, native crash reporting is disabled. JS error reporting (init(), post(), postFeedback(), setUser(), setAttribute(), ErrorBoundary) still works via an HTTP fallback. A warning is logged at init() to let you know native crash reporting is inactive.

To test full native crash reporting, use a release build (see Testing Native Crashes below). Development builds include a debugger that intercepts crashes before BugSplat can capture them.

Testing Native Crashes

To test native crash reporting, you must run a release build — the debugger intercepts crashes in debug builds.

# iOS
npx expo run:ios --configuration Release

# Android
npx expo run:android --variant release

iOS: Crash reports are captured at crash time by PLCrashReporter and uploaded on the next app launch when init() is called again. After triggering a test crash, relaunch the app and call init() to upload the pending report.

Android: Crash reports are captured and uploaded immediately at crash time by the Crashpad handler process.

Testing Native Hangs (ANRs)

hang() blocks the main thread in a native loop. The handling differs slightly per platform:

  • Android: the system ANR detector fires after ~5 seconds of unresponsive input and BugSplat captures a dump with symbolicated native frames immediately.
  • iOS: the bundled BugSplat-Apple hang tracker persists a fatal-hang report once the main thread stays unresponsive past its threshold (~2s). The report uploads on the next app launch.

To trigger and verify:

  1. Call hang() (e.g. from a debug button).
  2. Android: tap the screen a few times — the system "App not responding" dialog appears, dump is captured.
  3. iOS: force-quit from the app switcher once the UI freezes (or wait for the OS watchdog).
  4. Relaunch the app. The hang report uploads on next launch alongside any pending crash reports.

Troubleshooting

Android: Crashes not uploading on emulator

The Crashpad handler process requires native libraries to be extracted to disk. The @bugsplat/expo config plugin turns on legacy JNI packaging automatically by setting expo.useLegacyPackaging=true in android/gradle.properties (the same setting as expo-build-properties' android.useLegacyPackaging, so don't set that one to false). It does not set android:extractNativeLibs in the manifest, because the Android Gradle Plugin used by Expo SDK 58 fails the build on it. If you're still not seeing crashes:

  • Use a google_apis emulator image (not google_apis_playstore). The Play Store emulator images have restrictions that prevent Crashpad's handler process from executing.
  • Alternatively, test on a physical Android device where this is not an issue.

iOS: No crash report after test crash

Make sure you relaunch the app and call init() again after the crash. PLCrashReporter saves the crash to disk and uploads it on the next launch.

License

MIT

版本与发布记录

@bugsplat/expo 共发布 14 个版本,首次发布于 2026/03/24;过去 12 个月发布了 14 个版本。

版本发布日期距今
0.8.2latest2026/10/2712 天前
0.8.12026/10/1623 天前
0.8.02026/10/1623 天前
0.7.02026/05/195 个月前
0.6.02026/04/196 个月前
0.5.12026/04/186 个月前

关于 @bugsplat/expo 的常见问题

@bugsplat/expo 是什么?

BugSplat crash and error reporting for Expo apps across iOS, Android, and Web

如何安装 @bugsplat/expo?

在项目目录运行 npm install @bugsplat/expo,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:expo(*)、react(*)、expo-blob(>=55.0.0)、react-native(*)、@bugsplat/react(^2.1.1)、expo-file-system(*)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bugsplat/expo 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bugsplat/expo 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@bugsplat/expo 可以商用吗?

@bugsplat/expo 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bugsplat/expo 的下载量有多少?

根据 npm 官方下载统计,@bugsplat/expo 在 2026/10/09 – 2026/11/07 这 30 天内被下载 3,419 次,日均约 114 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bugsplat/expo 还在维护吗?

最新版本 0.8.2 发布于 2026/10/27(12 天前);至今共发布 14 个版本,其中过去 12 个月发布了 14 个;首次发布于 2026/03/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。