Crash Reporting with Doctor SDKs

Capture exceptions from React, React Native, and Flutter apps — upload crash reports as searchable Markdown documents with breadcrumbs, device context, and offline queuing.

intermediate18 min read

What You'll Build

The Doctor SDKs turn your Lifestream Vault into a crash reporting backend. When an exception occurs in your app, the SDK captures the error, enriches it with breadcrumbs, device context, and session info, formats it as a Markdown document with YAML frontmatter, and uploads it to a vault. Every crash report becomes a first-class, searchable document — no separate error-tracking service required.

Two SDKs are available:

PlatformPackageVersion
React / React Native / Node.js@lifestreamdynamics/doctor1.0.6
Dart / Flutterlifestream_doctor1.0.1

Both SDKs produce identical Markdown output and share the same HMAC request-signing protocol, so crash reports from all your platforms land in a single vault with consistent structure.

By the end of this guide you will have:

  • Installed the Doctor SDK for your platform
  • Configured a write-only API key scoped to a crash reports vault
  • Initialized the SDK with consent management
  • Captured exceptions and messages with breadcrumbs and device context
  • Set up automatic error boundaries and global handlers
  • Organized crash reports with auto-tag hooks
  • Visualized crash patterns using the kanban board and calendar timeline

Prerequisites

  • A Lifestream Vault account (any tier)
  • A vault dedicated to crash reports (recommended)
  • A vault-scoped, write-only API key — see the API Keys guide for setup
  • React / TypeScript: Node.js 18+, React 18+
  • Flutter / Dart: Dart 3.3+, Flutter 3.x
  • For auto-tag hooks: Pro or Business tier

Install the SDK

Both SDKs are published to their respective package registries and ship with full type definitions. Choose the tab matching your platform.

bash
# npm
npm install @lifestreamdynamics/doctor

# yarn
yarn add @lifestreamdynamics/doctor

# pnpm
pnpm add @lifestreamdynamics/doctor

React Native users install the same npm package. For persistent offline queuing and device context, also import the React Native adapter:

import { AsyncStorageBackend, installReactNativeHandlers, getReactNativeDeviceContext }
  from '@lifestreamdynamics/doctor/react-native';

Source code & issues:

Create a Crash Reports API Key

The Doctor SDK authenticates with your vault via an API key. For crash reporting, the key should be:

  • Write-only — the SDK only uploads documents; it never reads them
  • Vault-scoped — restricted to your crash reports vault only
  • Prefixed with lsv_k_ (all Lifestream Vault API keys use this prefix)

The full key is only shown once at creation time — copy and store it immediately. See the API Keys & Scoping guide for details on key rotation and best practices.

markdown
1. Go to Settings -> API Keys -> New Key
2. Name: "Crash Reports (Production)"
3. Scopes: select "write" only (uncheck "read")
4. Vault: select your crash reports vault
5. Click Create — copy the key immediately

Never embed the API key in client-side JavaScript bundles served to end users — the key would be visible in browser dev tools. For web apps, proxy crash reports through your own backend. For React Native and Flutter, the key is compiled into the native binary, which provides reasonable protection but is not truly secret — consider enabling HMAC request signing (enableRequestSigning: true, on by default) for additional security.

Initialize the SDK

Create a single LifestreamDoctor instance at app startup. The most important options are:

OptionDefaultDescription
apiUrl(required)Your vault's base URL
vaultId(required)The crash reports vault ID
apiKey(required)Write-only API key (lsv_k_ prefix)
environment'production'Tags every report (e.g. 'staging', 'preview')
enabledtrueSet false to disable in development
pathPrefix'crash-reports'Document path prefix
tags[]Custom tags added to every report
beforeSendFilter/transform reports before upload (return null to discard)
maxBreadcrumbs50Breadcrumb buffer size
enableRequestSigningtrueHMAC-SHA256 request signing
debugfalseLog SDK activity to console
typescript
import { LifestreamDoctor } from '@lifestreamdynamics/doctor';

const doctor = new LifestreamDoctor({
  apiUrl: process.env.VAULT_URL!,
  vaultId: process.env.VAULT_CRASH_REPORTS_ID!,
  apiKey: process.env.VAULT_CRASH_API_KEY!,
  environment: process.env.NODE_ENV ?? 'production',
  enabled: process.env.NODE_ENV === 'production',
  pathPrefix: 'crash-reports',
  tags: ['frontend', 'v2.1.0'],
  debug: false,
});

export default doctor;

Use a dedicated crash reports vault to keep crash data separate from your content documents. Customize pathPrefix to organize by platform — for example, 'crashes/ios' or 'crashes/web' — if you want separate folders within the same vault. The SDK auto-organizes reports by date: crash-reports/2026-03-22/typeerror-a1b2c3d4.md.

Capture Exceptions

captureException() is the core method. It builds a CrashReport, formats it as Markdown with YAML frontmatter, and uploads it to your vault. If the upload fails (offline, network error), the report is queued for later retry via flushQueue().

You can also use captureMessage() for non-exception events like warnings or informational notes.

Extras parameter options:

FieldTypeDescription
severityfatal | error | warning | infoSeverity level (default: error)
extraObject / MapArbitrary key-value context (max 50KB)
tagsstring[] / ListPer-report tags (merged with global tags)
componentStackstringReact component tree (auto-set by error boundary)
typescript
import doctor from './doctor';

// Capture an exception with context
async function handleCheckout(orderId: string) {
  try {
    await processPayment(orderId);
  } catch (error) {
    await doctor.captureException(error as Error, {
      severity: 'error',
      extra: { orderId, route: '/checkout' },
      tags: ['checkout', 'payments'],
    });
    // Show user-facing error UI...
  }
}

// Capture a non-exception message
await doctor.captureMessage(
  'Payment retry succeeded after 2 attempts',
  'warning',
  { orderId: 'ord_123', attempts: 2 },
);

Auto-generated tags: Every report automatically receives severity:<level>, env:<environment>, and the lowercased error name (e.g. typeerror, stateerror) as tags. These are merged with any global tags from initialization and per-capture tags, making all reports searchable by severity, environment, and error type in the vault.

Automatic Error Capture

Instead of wrapping every function in try/catch, set up automatic error capture at the app level. The approach differs by platform:

  • React (web): Error boundary component wrapping your app tree
  • React Native: Global error handler + unhandled promise rejection tracker
  • Flutter: FlutterError.onError + PlatformDispatcher.instance.onError
typescript
import doctor from './doctor';

// Create the error boundary component
const DoctorErrorBoundary = doctor.createErrorBoundary();

// Wrap your app tree
function App() {
  return (
    <DoctorErrorBoundary>
      <Router>
        <AppRoutes />
      </Router>
    </DoctorErrorBoundary>
  );
}

// The error boundary automatically:
// - Captures the error with severity 'fatal'
// - Includes the React component stack
// - Renders a fallback UI (or customize with a fallback prop)

For React Native apps, installReactNativeHandlers() is the recommended one-call setup. It wires both the global JS error handler (ErrorUtils.setGlobalHandler) and the unhandled promise rejection tracker, and sets up device context collection automatically. The returned cleanup function unregisters the handlers on app unmount.

Offline Queue and Retry

When a crash report fails to upload (network error, server unavailable), the SDK serializes it to the storage backend for later retry. Call flushQueue() to retry all pending reports — for example on app startup, on network reconnection, or periodically.

The queue holds up to 50 entries. Reports that fail 5 attempts are moved to dead letter status and discarded. The FlushResult object tells you how many reports were sent, failed, or dead-lettered.

typescript
import doctor from './doctor';

// Flush on app startup
const result = await doctor.flushQueue();
console.log(`Sent: ${result.sent}, Failed: ${result.failed}`);

// Flush on network reconnection (web)
window.addEventListener('online', async () => {
  await doctor.flushQueue();
});

// For React Native, use @react-native-community/netinfo:
// NetInfo.addEventListener(state => {
//   if (state.isConnected) doctor.flushQueue();
// });

The default MemoryStorage backend loses queued reports when the process exits. For mobile apps, use AsyncStorageBackend (React Native) or implement the StorageBackend interface with shared_preferences (Flutter) to persist the queue across app restarts.

Organize Crash Reports with Hooks

Doctor uploads crash reports to a predictable path structure (crash-reports/YYYY-MM-DD/...), which makes them perfect targets for document operation hooks. Hooks run server-side whenever a document is created, so you can automatically add organizational tags to every incoming crash report without any client-side changes.

Use the document_operation action type with operation: 'add_tag' to tag documents automatically. Combine this with a triggerFilter using pathPattern (glob syntax) to scope hooks to your crash reports folder.

See the Automate Your Vault guide for full hooks documentation.

typescript
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

const { client } = await LifestreamVaultClient.login(
  'https://vault.lifestreamdynamics.com',
  'you@example.com',
  'your-password',
);

const vaultId = 'your-crash-vault-id';

// Hook 1: Tag all crash reports as "needs-triage"
await client.hooks.create(vaultId, {
  name: 'Crash Report Triage',
  triggerEvent: 'document.created',
  triggerFilter: { pathPattern: 'crash-reports/**' },
  actionType: 'document_operation',
  actionConfig: { operation: 'add_tag', tag: 'needs-triage' },
});

// Hook 2: Tag all crash reports as "crash-report"
await client.hooks.create(vaultId, {
  name: 'Crash Report Label',
  triggerEvent: 'document.created',
  triggerFilter: { pathPattern: 'crash-reports/**' },
  actionType: 'document_operation',
  actionConfig: { operation: 'add_tag', tag: 'crash-report' },
});
Plan Required

Document operation hooks require a Pro or Business subscription. On the Free tier, crash reports are still uploaded and searchable, but server-side automatic tagging is not available. You can add tags at capture time using the SDK's tags option instead.

Visualize Crashes with Kanban and Calendar

Because Doctor writes structured YAML frontmatter into every crash report, the vault's kanban board and calendar timeline views work out of the box with no additional configuration.

Kanban board

The kanban board groups documents by any frontmatter key. For a crash reports vault, the most useful groupings are:

Group byColumns createdUse case
severityfatal | error | warning | infoTriage board — prioritize by impact
deviceios | android | webPlatform view — isolate platform-specific issues
environmentproduction | staging | developmentEnvironment view — separate real crashes from test noise
appVersion2.0.0 | 2.1.0 | 2.2.0Version view — track regressions per release

Drag a crash report between columns to update its frontmatter — for example, grouping by severity and moving a triaged report to a custom status column updates the document. Combined with the document operation hooks from the previous section, the kanban board becomes a lightweight crash triage workflow.

Calendar timeline

Every crash report includes a date field in its frontmatter, so reports appear on the activity heatmap as document creation events. This gives you:

  • Daily crash volume — visible as color intensity on the heatmap cells
  • Regression detection — a spike in crashes after a deploy shows as a hot cluster
  • Pattern recognition — recurring crashes on specific days of the week may indicate scheduled jobs or traffic patterns

Click any day to see all crash reports filed on that date. The timeline view shows the time, severity, and error name for each report.

Practical example

After deploying v2.1.0 on Monday, you open the calendar view on Wednesday and notice the heatmap shows an unusually hot Tuesday. You click Tuesday's cell and see 47 crash reports — mostly TypeError with device: android.

You switch to the kanban board and group by severity. The fatal column has 12 reports, all tagged needs-triage by the document operation hook. You open one and find a null reference in the checkout flow — the stack trace points to CheckoutScreen.tsx:142 and the breadcrumbs show the user navigated from the cart page.

Within minutes, you know exactly what broke, when it broke, and which platform is affected — all from your vault's built-in views.

See the Manage Projects with Calendar guide for more on the activity heatmap and calendar features.

The kanban board and calendar timeline are available in the web UI for any vault — no special configuration needed. The frontmatter fields Doctor generates (severity, device, os, environment, appVersion) are standard YAML keys that the kanban grouping system reads automatically.

What's Next

You now have crash reporting flowing into your vault with automatic tagging and visual triage tools. Here are some next steps to explore:

SDK resources