Using Sentry in Flutter
By Ann Tech · 31 October 2025
Sentry catches errors in production and gives you the context to reproduce and fix them: stack trace, device info, OS version, recent user actions, and custom context you attach. Here is how to integrate it properly in a Flutter app.
Setup
dependencies:
sentry_flutter: ^8.0.0
Initialization
Wrap runApp with Sentry's initializer:
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'https://[email protected]/project-id';
options.tracesSampleRate = 0.2; // 20% of transactions for performance
options.profilesSampleRate = 0.1; // 10% of traces for profiling
options.environment = const String.fromEnvironment('APP_ENV', defaultValue: 'production');
options.release = '${packageInfo.appName}@${packageInfo.version}+${packageInfo.buildNumber}';
// Attach screenshots on error (iOS/Android only)
options.attachScreenshot = true;
// Don't send events in debug mode
options.beforeSend = (event, hint) {
if (event.level == SentryLevel.debug) return null;
return event;
};
},
appRunner: () => runApp(const MyApp()),
);
}
Catching uncaught errors
Sentry's init wrapper catches most errors automatically. Add explicit handlers for the remaining cases:
// Flutter framework errors
FlutterError.onError = (details) {
FlutterError.presentError(details);
Sentry.captureException(
details.exception,
stackTrace: details.stack,
);
};
// Async errors not caught by Flutter framework
PlatformDispatcher.instance.onError = (error, stack) {
Sentry.captureException(error, stackTrace: stack);
return true;
};
Capturing exceptions manually
try {
final order = await orderRepo.createOrder(request);
// ...
} catch (exception, stackTrace) {
await Sentry.captureException(
exception,
stackTrace: stackTrace,
hint: Hint.withMap({'order_request': request.toJson()}),
);
// Show user-facing error
}
Adding context with scope
Context makes errors actionable:
// Set user context (cleared on sign out)
Sentry.configureScope((scope) {
scope.setUser(SentryUser(
id: user.id,
email: user.email,
data: {'plan': user.plan, 'region': user.region},
));
});
// Add tags for filtering in Sentry dashboard
Sentry.configureScope((scope) {
scope.setTag('flavor', AppFlavor.current.name);
scope.setTag('api_env', ApiConfig.baseUrl);
});
// Add extra context
Sentry.configureScope((scope) {
scope.setExtra('cart_item_count', cart.items.length);
scope.setExtra('feature_flags', remoteConfig.activeFlags);
});
Breadcrumbs
Breadcrumbs trace what the user did before the crash:
// Automatic breadcrumbs: navigation, network requests, console output
// Manual breadcrumbs:
Sentry.addBreadcrumb(Breadcrumb(
message: 'User tapped checkout',
category: 'user.action',
level: SentryLevel.info,
data: {'cart_total': cart.total.toString()},
));
// Navigation breadcrumbs with go_router:
GoRouter(
observers: [SentryNavigatorObserver()],
routes: [...],
)
Performance monitoring
Track slow operations:
Future<Order> placeOrder(OrderRequest request) async {
final transaction = Sentry.startTransaction(
'order.place',
'task',
bindToScope: true,
);
try {
final span = transaction.startChild('order.validate');
await validateOrder(request);
await span.finish();
final apiSpan = transaction.startChild('order.api.create');
final order = await api.createOrder(request);
await apiSpan.finish();
await transaction.finish(status: const SpanStatus.ok());
return order;
} catch (e, stack) {
await transaction.finish(status: const SpanStatus.internalError());
rethrow;
}
}
Source maps for release builds
In release mode, Dart is compiled to native code and stack traces are symbolicated. Upload debug symbols to Sentry:
# Install Sentry CLI
npm install -g @sentry/cli
# Upload Android symbols
flutter build apk --obfuscate --split-debug-info=./debug-info
sentry-cli upload-dif ./debug-info --org your-org --project your-project
# iOS symbols are uploaded via Xcode build phase or Fastlane
Environment-based DSN
Use different Sentry projects for dev/staging/production:
const dsn = String.fromEnvironment('SENTRY_DSN', defaultValue: '');
options.dsn = dsn.isEmpty ? '' : dsn; // Empty DSN disables Sentry
In CI:
- name: Build
run: |
flutter build apk \
--dart-define=SENTRY_DSN=${{ secrets.SENTRY_DSN_PROD }} \
--dart-define=APP_ENV=production
Common pitfalls
Not scrubbing PII. Sentry captures request data, user input, and screenshots. Configure beforeSend to remove sensitive fields:
options.beforeSend = (event, hint) {
// Remove sensitive headers
event.request?.headers?.remove('Authorization');
return event;
};
100% traces sample rate in production. Performance monitoring has overhead. Start at 10-20% for production. Use tracesSampler for dynamic rates (higher for critical flows, lower for background tasks).
Not clearing the user on sign-out. Sentry persists user context across sessions. Call Sentry.configureScope((s) => s.setUser(null)) when the user signs out.
Sign in to like, dislike, or report.