AI-powered crash analysis is now available on all plans — including Free.Read the crash analysis guide

Flutter SDK

Add BugsPulse to your Flutter app to capture crashes, replay sessions, monitor network requests, and track custom events — with no video recording and no PII.

Installation

Add bugspulse and its peer dependency to your pubspec.yaml:

dependencies:
  bugspulse: ^0.1.0
  connectivity_plus: ^6.0.0   # required peer dependency

Then fetch packages:

flutter pub get

Initialize

Call BugsPulse.init() as early as possible — before runApp(). The SDK is a no-op if it is called a second time, so it is safe to call in hot-restart scenarios.

import 'package:bugspulse/bugspulse.dart';
import 'package:flutter/widgets.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await BugsPulse.init(BugsPulseConfig(
    apiKey: 'pr_your_key_here',
    environment: 'production',
    appVersion: '2.4.1',
    appBuildNumber: '241',
  ));

  runApp(const MyApp());
}
Where do I get an API key? Create a project in your BugsPulse dashboard, then copy the key from the project Settings page.

Route tracking

Pass BugsPulse.navigatorObserver to your MaterialApp (or CupertinoApp). The observer emits a navigate event on every route push/pop so you can see the full screen path a user followed before a crash.

MaterialApp(
  navigatorObservers: [BugsPulse.navigatorObserver],
  home: const HomeScreen(),
);

If you use GoRouter or Auto Route, register the observer at the router level:

// GoRouter example
final _router = GoRouter(
  observers: [BugsPulse.navigatorObserver],
  routes: [...],
);

Network monitoring

Add a Dio interceptor to capture every HTTP request and response. The SDK automatically scrubs sensitive query parameters (anything matching authorization, password, token, secret, card, or cvv) before storing the URL.

import 'package:dio/dio.dart';

final dio = Dio();

dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      options.extra['_bp_start'] = DateTime.now().millisecondsSinceEpoch;
      handler.next(options);
    },
    onResponse: (response, handler) {
      final start = response.requestOptions.extra['_bp_start'] as int? ?? 0;
      BugsPulse.logNetworkRequest(
        method: response.requestOptions.method,
        url: response.requestOptions.uri.toString(),
        statusCode: response.statusCode,
        duration: DateTime.now().millisecondsSinceEpoch - start,
      );
      handler.next(response);
    },
    onError: (error, handler) {
      final start = error.requestOptions.extra['_bp_start'] as int? ?? 0;
      BugsPulse.logNetworkRequest(
        method: error.requestOptions.method,
        url: error.requestOptions.uri.toString(),
        statusCode: error.response?.statusCode,
        duration: DateTime.now().millisecondsSinceEpoch - start,
        error: error.message,
      );
      handler.next(error);
    },
  ),
);
Using http or other clients? Wrap your request logic manually with BugsPulse.logNetworkRequest() — the method accepts any HTTP library. The call is a no-op when the SDK is not initialized.

Crash reporting

Automatic (zero config)

When captureCrashes: true (the default), the SDK hooks into FlutterError.onError and PlatformDispatcher.onError to capture all unhandled Flutter and platform errors. Fatal crashes set the session status to crashed in the dashboard.

Handled exceptions

Report caught errors that you want to track without re-throwing:

try {
  await riskyOperation();
} catch (error, stackTrace) {
  BugsPulse.captureException(error, stackTrace);
  // continue normal execution
}

Crash grouping

The server groups crashes by a SHA-256 hash of the first five stack frames. Each unique crash group appears once in the Crashes dashboard and its occurrence counter increments on every repeat — you won't see thousands of duplicate entries.

Custom event tracking

Track any in-app action with optional properties. Events are batched in memory and flushed every flushInterval (default 5 s):

// Simple event
BugsPulse.track('checkout_started');

// Event with properties
BugsPulse.track('item_added', {
  'product_id': 'sku_123',
  'quantity': 2,
  'price_usd': 29.99,
});

Custom events appear in the session replay timeline alongside navigation and network events.

Identifying users

Associate sessions with your own user identifier (an internal ID, not an email) so you can look up all sessions for a specific user:

// After sign-in
BugsPulse.setUser(currentUser.id);   // e.g. 'usr_7f2a3b'
Never pass PII here. Pass an opaque internal ID, not an email address or display name. The value is stored in plaintext alongside session metadata.

App lifecycle integration

Call BugsPulse.endSession() when the app moves to the background. Without this, sessions stay in the active state and their duration is never recorded in analytics. Attach WidgetsBindingObserver in your root widget:

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.paused) {
      BugsPulse.endSession();
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      navigatorObservers: [BugsPulse.navigatorObserver],
      home: const HomeScreen(),
    );
  }
}

Privacy & redaction

The SDK never captures keyboard input, clipboard content, or images. For network monitoring, URLs are scrubbed of known sensitive query parameters automatically. To redact additional patterns, set redactedFields:

BugsPulseConfig(
  apiKey: 'pr_your_key',
  redactedFields: const ['user_token', 'promo_code', 'ssn'],
)

Any query parameter whose key contains one of those strings (case-insensitive) is replaced with [redacted] before the URL is sent to the server.

Session sampling

To reduce session volume on high-traffic apps, set sessionSamplingRate to a value between 0.0 and 1.0. For example, 0.1 captures 10% of sessions:

BugsPulseConfig(
  apiKey: 'pr_your_key',
  sessionSamplingRate: 0.1,  // capture 10 % of sessions
)

When a session is sampled out, all SDK methods become no-ops for that app launch. The navigator observer still returns a valid (but inert) object so you never need to null-check it.

Configuration reference

OptionTypeDefaultDescription
apiKeyStringrequiredProject API key from the dashboard.
apiUrlStringhttps://api.bugspulse.comOverride the ingest endpoint. Only needed for local development.
environmentString'production'Tag sessions by environment (production, staging, …).
appVersionString?nullApp version string (e.g. '2.4.1'). Shown on crash groups.
appBuildNumberString'0'Build/bundle number for filtering crashes by build.
captureCrashesbooltrueInstall FlutterError and PlatformDispatcher handlers.
captureNetworkRequestsbooltrueReserved for future auto-capture support.
captureTouchesbooltrueInclude touch coordinates in event payloads.
sessionSamplingRatedouble1.0Fraction of sessions to capture (0.0–1.0).
flushIntervalDuration5 secondsHow often the event queue is sent to the server.
maxOfflineQueueSizeint1000Maximum events held in memory when offline.
redactedFieldsList<String>[]Additional query-parameter keys to redact from URLs.

API reference

MethodDescription
BugsPulse.init(config)Initialize the SDK. Must be called before runApp(). Idempotent.
BugsPulse.track(name, [props])Emit a named custom event with optional string/number properties.
BugsPulse.setUser(userId)Attach an opaque user identifier to the current session.
BugsPulse.captureException(error, stackTrace)Report a handled exception without re-throwing.
BugsPulse.logNetworkRequest({…})Manually record an HTTP request (use with non-Dio clients).
BugsPulse.endSession()Flush the queue, send session end, and reset all state.
BugsPulse.navigatorObserverNavigatorObserver to pass to MaterialApp / GoRouter.

Troubleshooting

Sessions appear but crashes are missing

Make sure WidgetsFlutterBinding.ensureInitialized() is called before BugsPulse.init(). The crash handlers require the binding to be active.

Session duration shows 0 or is missing

Call BugsPulse.endSession() when the app is paused (see App lifecycle integration above). Sessions without an end event stay in the active state and their duration is not calculated.

Network requests not appearing

The Dio interceptor must be added before any requests are made. If you create a Dio singleton, add the interceptor in its initializer. Also confirm BugsPulse.init() has resolved before the first request fires.

401 errors on ingest

Your API key is tied to a specific project. Make sure you copied the key from the correct project in the dashboard. Keys start with pr_.