
Sentry Flutter Sdk
- 1.7k installs
- 243 repo stars
- Updated July 27, 2026
- getsentry/sentry-for-ai
sentry-flutter-sdk is a setup wizard for Sentry error monitoring, tracing, replay, and profiling in Flutter apps.
About
The sentry-flutter-sdk skill is an opinionated wizard for sentry_flutter >=9.14.0 (February 2026 stable). Phase 1 Detect runs pubspec and lib scans for existing SentryFlutter.init, Flutter/Dart SDK versions, navigation (go_router, auto_route), HTTP (dio, http), databases (sqflite, drift, hive), state management, GraphQL, Firebase, platform targets, and sibling backend repos for cross-linking. It supports Android, iOS, macOS, Linux, Windows, and Web with native crash, ANR, and app hang monitoring. Child of sentry-sdk-setup in the SKILL_TREE; disable-model-invocation is true so users explicitly request setup. Phases progress through detect, recommend options (tracing, replay, profiling, logging), implement initialization and integrations, and verify against docs.sentry.io/platforms/flutter/. Use when users ask to add Sentry to Flutter, install sentry_flutter, or configure mobile error monitoring.
- Phase 1 Detect scans pubspec, lib init, navigation, HTTP, DB, and platforms.
- Covers errors, tracing, profiling, session replay, and logging for Flutter.
- Supports Android, iOS, macOS, Linux, Windows, and Web targets.
- Detects go_router, dio, bloc, Firebase, and backend siblings for integrations.
- Opinionated wizard aligned with sentry_flutter 9.14+ stable APIs.
Sentry Flutter Sdk by the numbers
- 1,737 all-time installs (skills.sh)
- +46 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #130 of 1,048 Mobile Development skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
sentry-flutter-sdk capabilities & compatibility
- Capabilities
- project detection scans · sdk initialization wizard · tracing and replay setup · navigation and http integrations · multi platform flutter support
- Works with
- sentry
- Use cases
- debugging · devops
npx skills add https://github.com/getsentry/sentry-for-ai --skill sentry-flutter-sdkAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.7k |
|---|---|
| repo stars | ★ 243 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | getsentry/sentry-for-ai ↗ |
How do I add and configure Sentry in a Flutter or Dart application?
Guide complete Sentry Flutter SDK setup for errors, tracing, profiling, session replay, and logging across mobile and desktop targets.
Who is it for?
Flutter teams adding Sentry monitoring across mobile and desktop targets.
Skip if: Backend-only Sentry setup without a Flutter client (use sibling SDK skills).
When should I use this skill?
User asks to add Sentry to Flutter, sentry_flutter setup, or Flutter crash monitoring.
What you get
Initialized sentry_flutter with tracing, replay, and relevant navigation/HTTP integrations verified.
- configured sentry_flutter SDK
- DSN and monitoring initialization
By the numbers
- Supports 6 deployment platforms: Android, iOS, macOS, Linux, Windows, Web
- Apache-2.0 licensed skill under sentry-sdk-setup parent
Files
All Skills > SDK Setup > Flutter SDK
Sentry Flutter SDK
Opinionated wizard that scans your Flutter or Dart project and guides you through complete Sentry setup — error monitoring, tracing, session replay, logging, profiling, and ecosystem integrations.
Invoke This Skill When
- User asks to "add Sentry to Flutter" or "set up Sentry" in a Flutter or Dart app
- User wants error monitoring, tracing, profiling, session replay, or logging in Flutter
- User mentions
sentry_flutter,sentry_dart, mobile error tracking, or Sentry for Flutter - User wants to monitor native crashes, ANRs, or app hangs on iOS/Android
Note: SDK versions and APIs below reflect sentry_flutter ≥9.14.0 (current stable, February 2026).Always verify against docs.sentry.io/platforms/flutter/ before implementing.
---
Phase 1: Detect
Run these commands to understand the project before making any recommendations:
# Detect Flutter project type and existing Sentry
cat pubspec.yaml | grep -E '(sentry|flutter|dart)'
# Check SDK version
cat pubspec.yaml | grep -A2 'environment:'
# Check for existing Sentry initialization
grep -r "SentryFlutter.init\|Sentry.init" lib/ 2>/dev/null | head -5
# Detect navigation library
grep -E '(go_router|auto_route|get:|beamer|routemaster)' pubspec.yaml
# Detect HTTP client
grep -E '(dio:|http:|chopper:)' pubspec.yaml
# Detect database packages
grep -E '(sqflite|drift|hive|isar|floor)' pubspec.yaml
# Detect state management (for integration patterns)
grep -E '(flutter_bloc|riverpod|provider:|get:)' pubspec.yaml
# Detect GraphQL
grep -E '(graphql|ferry|gql)' pubspec.yaml
# Detect Firebase
grep -E '(firebase_core|supabase)' pubspec.yaml
# Detect backend for cross-link
ls ../backend/ ../server/ ../api/ 2>/dev/null
find .. -maxdepth 3 \( -name "go.mod" -o -name "requirements.txt" -o -name "Gemfile" -o -name "*.csproj" \) 2>/dev/null | grep -v flutter | head -10
# Detect platform targets
ls android/ ios/ macos/ linux/ windows/ web/ 2>/dev/nullWhat to determine:
| Question | Impact |
|---|---|
sentry_flutter already in pubspec.yaml? | Skip install, jump to feature config |
Dart SDK >=3.5? | Required for sentry_flutter ≥9.0.0 |
go_router or auto_route present? | Use SentryNavigatorObserver — specific patterns apply |
dio present? | Recommend sentry_dio integration |
sqflite, drift, hive, isar present? | Recommend matching sentry_* DB package |
Has android/ and ios/ directories? | Full mobile feature set available |
Has web/ directory only? | Session Replay and Profiling unavailable |
Has macos/ directory? | Profiling available (alpha) |
| Backend directory detected? | Trigger Phase 4 cross-link |
---
Phase 2: Recommend
Present a concrete recommendation based on what you found. Don't ask open-ended questions — lead with a proposal:
Recommended (core coverage — always set up these):
- ✅ Error Monitoring — captures Dart exceptions, Flutter framework errors, and native crashes (iOS + Android)
- ✅ Tracing — auto-instruments navigation, app start, network requests, and UI interactions
- ✅ Session Replay — captures widget tree screenshots for debugging (iOS + Android only)
Optional (enhanced observability):
- ⚡ Profiling — CPU profiling; iOS and macOS only (alpha)
- ⚡ Logging — structured logs via
Sentry.logger.*andsentry_loggingintegration - ⚡ Metrics — counters, gauges, distributions (SDK ≥9.11.0)
Platform limitations — be upfront:
| Feature | Platforms | Notes |
|---|---|---|
| Session Replay | iOS, Android | Not available on macOS, Linux, Windows, Web |
| Profiling | iOS, macOS | Alpha status; not available on Android, Linux, Windows, Web |
| Native crashes | iOS, Android, macOS | NDK/signal handling; Linux/Windows/Web: Dart exceptions only |
| App Start metrics | iOS, Android | Not available on desktop/web |
| Slow/frozen frames | iOS, Android, macOS | Not available on Linux, Windows, Web |
| Crons | N/A | Not available in the Flutter/Dart SDK |
Propose: "For your Flutter app targeting iOS/Android, I recommend Error Monitoring + Tracing + Session Replay. Want me to also add Logging and Profiling (iOS/macOS alpha)?"
---
Phase 3: Guide
Determine Your Setup Path
| Project type | Recommended setup |
|---|---|
| Any Flutter app | Wizard CLI (handles pubspec, init, symbol upload) |
| Manual preferred | Path B below — pubspec.yaml + main.dart |
| Dart-only (CLI, server) | Path C below — pure sentry package |
---
Path A: Wizard CLI (Recommended)
You need to run this yourself — the wizard opens a browser for login and requires interactive input that the agent can't handle. Copy-paste into your terminal:
>
```bash
brew install getsentry/tools/sentry-wizard && sentry-wizard -i flutter
```
>
It handles org/project selection, addssentry_fluttertopubspec.yaml, updatesmain.dart, configuressentry_dart_pluginfor debug symbol upload, and adds build scripts. Here's what it creates/modifies:
>
| File | Action | Purpose |
|------|--------|---------|
|pubspec.yaml| Addssentry_flutterdependency andsentry:config block | SDK + symbol upload config |
|lib/main.dart| Wrapsmain()withSentryFlutter.init()| SDK initialization |
| android/app/build.gradle | Adds Proguard config reference | Android obfuscation support || .sentryclirc | Auth token and org/project config | Symbol upload credentials |>
Once it finishes, come back and skip to [Verification](#verification).
If the user skips the wizard, proceed with Path B (Manual Setup) below.
---
Path B: Manual — Flutter App
Step 1 — Install
flutter pub add sentry_flutterOr add to pubspec.yaml manually:
dependencies:
flutter:
sdk: flutter
sentry_flutter: ^9.14.0Then run:
flutter pub getStep 2 — Initialize Sentry in `lib/main.dart`
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_SENTRY_DSN';
options.sendDefaultPii = true;
// Tracing
options.tracesSampleRate = 1.0; // lower to 0.1–0.2 in production
// Profiling (iOS and macOS only — alpha)
options.profilesSampleRate = 1.0;
// Session Replay (iOS and Android only)
options.replay.sessionSampleRate = 0.1;
options.replay.onErrorSampleRate = 1.0;
// Structured Logging (SDK ≥9.5.0)
options.enableLogs = true;
options.environment = const bool.fromEnvironment('dart.vm.product')
? 'production'
: 'development';
},
// REQUIRED: wrap root widget to enable screenshots, replay, user interaction tracing
appRunner: () => runApp(SentryWidget(child: MyApp())),
);
}Step 3 — Add Navigation Observer
Add SentryNavigatorObserver to your MaterialApp or CupertinoApp:
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [
SentryNavigatorObserver(),
],
// Always name your routes for Sentry to track them
routes: {
'/': (context) => HomeScreen(),
'/profile': (context) => ProfileScreen(),
},
);
}
}For GoRouter:
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
final GoRouter router = GoRouter(
observers: [SentryNavigatorObserver()],
routes: [
GoRoute(
path: '/',
name: 'home', // name is REQUIRED for Sentry route tracking
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'profile/:id',
name: 'profile', // name is REQUIRED
builder: (context, state) => ProfileScreen(
id: state.pathParameters['id']!,
),
),
],
),
],
);Step 4 — Configure Debug Symbol Upload
Readable stack traces in Sentry require uploading debug symbols when building with --obfuscate.
Add to pubspec.yaml:
dev_dependencies:
sentry_dart_plugin: ^3.2.1
sentry:
project: YOUR_PROJECT_SLUG
org: YOUR_ORG_SLUG
auth_token: YOUR_AUTH_TOKEN # prefer SENTRY_AUTH_TOKEN env var instead
upload_debug_symbols: true
upload_sources: true
upload_source_maps: true # for WebBuild and upload:
# Android
flutter build apk \
--release \
--obfuscate \
--split-debug-info=build/debug-info \
--extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json
dart run sentry_dart_plugin
# iOS
flutter build ipa \
--release \
--obfuscate \
--split-debug-info=build/debug-info \
--extra-gen-snapshot-options=--save-obfuscation-map=build/app/obfuscation.map.json
dart run sentry_dart_plugin
# Web
flutter build web --release --source-maps
dart run sentry_dart_plugin---
Path C: Manual — Dart-Only (CLI / Server)
# pubspec.yaml
dependencies:
sentry: ^9.14.0import 'package:sentry/sentry.dart';
Future<void> main() async {
await Sentry.init(
(options) {
options.dsn = 'YOUR_SENTRY_DSN';
options.tracesSampleRate = 1.0;
options.enableLogs = true;
},
appRunner: myApp,
);
}---
Quick Reference: Full-Featured SentryFlutter.init()
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_SENTRY_DSN';
options.sendDefaultPii = true;
// Environment — detect release builds via dart.vm.product
options.environment = const bool.fromEnvironment('dart.vm.product')
? 'production'
: 'development';
// Release is auto-set on iOS/Android as "packageName@version+build"
// Override if needed:
// options.release = 'my-app@1.0.0+42';
// Error sampling — reduce to drop a fraction of errors in high-volume production
options.sampleRate = 1.0;
// Tracing — lower to 0.1–0.2 in high-traffic production
options.tracesSampleRate = 1.0;
// Profiling — iOS and macOS only (alpha); relative to tracesSampleRate
options.profilesSampleRate = 1.0;
// Session Replay — iOS and Android only (SDK ≥9.0.0)
options.replay.sessionSampleRate = 0.1; // record 10% of all sessions
options.replay.onErrorSampleRate = 1.0; // always record error sessions
// Privacy defaults — all text and images masked
options.privacy.maskAllText = true;
options.privacy.maskAllImages = true;
// Structured logging (SDK ≥9.5.0)
options.enableLogs = true;
// Attachments
options.attachScreenshot = true; // screenshot on error
options.attachViewHierarchy = true; // widget tree on error
// HTTP client
options.captureFailedRequests = true; // auto-capture HTTP errors
options.maxRequestBodySize = MaxRequestBodySize.small;
// Android specifics
options.anrEnabled = true; // ANR detection
options.enableNdkScopeSync = true; // sync scope to native
options.enableTombstone = false; // Android 12+ tombstone (opt-in)
// Navigation (Time to Full Display — opt-in)
options.enableTimeToFullDisplayTracing = true;
},
appRunner: () => runApp(SentryWidget(child: MyApp())),
);
}---
Navigation: Time to Full Display (TTFD)
TTID (Time to Initial Display) is always enabled. TTFD is opt-in:
// Enable in options:
options.enableTimeToFullDisplayTracing = true;Then report when your screen has loaded its data:
// Option 1: Widget wrapper (marks TTFD when child first renders)
SentryDisplayWidget(child: MyWidget())
// Option 2: Manual API call (after async data loads)
await _loadData();
SentryFlutter.currentDisplay()?.reportFullyDisplayed();---
For Each Agreed Feature
Walk through features one at a time. Load the reference file for each, follow its steps, then verify before moving on:
| Feature | Reference | Load when... |
|---|---|---|
| Error Monitoring | ${SKILL_ROOT}/references/error-monitoring.md | Always (baseline) |
| Tracing & Performance | ${SKILL_ROOT}/references/tracing.md | Always — navigation, HTTP, DB spans |
| Session Replay | ${SKILL_ROOT}/references/session-replay.md | iOS/Android user-facing apps |
| Profiling | ${SKILL_ROOT}/references/profiling.md | iOS/macOS performance-sensitive apps |
| Logging | ${SKILL_ROOT}/references/logging.md | Structured logging / log-trace correlation |
| Metrics | ${SKILL_ROOT}/references/metrics.md | Custom business metrics |
| Ecosystem Integrations | ${SKILL_ROOT}/references/ecosystem-integrations.md | HTTP clients, databases, GraphQL, state management |
For each feature: Read ${SKILL_ROOT}/references/<feature>.md, follow steps exactly, verify it works.
---
Configuration Reference
Core SentryFlutter.init() Options
| Option | Type | Default | Purpose |
|---|---|---|---|
dsn | string | — | Required. Project DSN. Env: SENTRY_DSN via --dart-define |
environment | string | — | e.g., "production", "staging". Env: SENTRY_ENVIRONMENT |
release | string | Auto on iOS/Android | "packageName@version+build". Env: SENTRY_RELEASE |
dist | string | — | Distribution identifier; max 64 chars. Env: SENTRY_DIST |
sendDefaultPii | bool | false | Include PII: IP address, user labels, widget text in replay |
sampleRate | double | 1.0 | Error event sampling (0.0–1.0) |
maxBreadcrumbs | int | 100 | Max breadcrumbs per event |
attachStacktrace | bool | true | Auto-attach stack traces to messages |
attachScreenshot | bool | false | Capture screenshot on error (mobile/desktop only) |
screenshotQuality | enum | high | Screenshot quality: full, high, medium, low |
attachViewHierarchy | bool | false | Attach JSON widget tree as attachment on error |
debug | bool | true in debug | Verbose SDK output. Never force `true` in production |
diagnosticLevel | enum | warning | Log verbosity: debug, info, warning, error, fatal |
enabled | bool | true | Disable SDK entirely (e.g., for testing) |
maxCacheItems | int | 30 | Max offline-cached envelopes (not supported on Web) |
sendClientReports | bool | true | Send SDK health reports (dropped events, etc.) |
reportPackages | bool | true | Report pubspec.yaml dependency list |
reportSilentFlutterErrors | bool | false | Capture FlutterErrorDetails.silent errors |
idleTimeout | Duration | 3000ms | Auto-finish idle user interaction transactions |
Tracing Options
| Option | Type | Default | Purpose |
|---|---|---|---|
tracesSampleRate | double | — | Transaction sample rate (0–1). Enable by setting >0 |
tracesSampler | function | — | Per-transaction sampling; overrides tracesSampleRate |
tracePropagationTargets | List | — | URLs to attach sentry-trace + baggage headers |
propagateTraceparent | bool | false | Also send W3C traceparent header (SDK ≥9.7.0) |
enableTimeToFullDisplayTracing | bool | false | Opt-in TTFD tracking per screen |
enableAutoPerformanceTracing | bool | true | Auto-enable performance monitoring |
enableUserInteractionTracing | bool | true | Create transactions for tap/click/long-press events |
enableUserInteractionBreadcrumbs | bool | true | Breadcrumbs for every tracked user interaction |
Profiling Options
| Option | Type | Default | Purpose |
|---|---|---|---|
profilesSampleRate | double | — | Profiling rate relative to tracesSampleRate. iOS/macOS only |
Native / Mobile Options
| Option | Type | Default | Purpose |
|---|---|---|---|
autoInitializeNativeSdk | bool | true | Auto-initialize native Android/iOS SDK layer |
enableNativeCrashHandling | bool | true | Capture native crashes (NDK, signal, Mach exception) |
enableNdkScopeSync | bool | true | Sync Dart scope to Android NDK |
enableScopeSync | bool | true | Sync scope data to native SDKs |
anrEnabled | bool | true | ANR detection (Android) |
anrTimeoutInterval | int | 5000 | ANR timeout in milliseconds (Android) |
enableWatchdogTerminationTracking | bool | true | OOM kill tracking (iOS) |
enableTombstone | bool | false | Android 12+ native crash info via ApplicationExitInfo |
attachThreads | bool | false | Attach all threads on crash (Android) |
captureNativeFailedRequests | bool | — | Native HTTP error capture, independent of Dart client (iOS/macOS, v9.11.0+) |
enableAutoNativeAppStart | bool | true | App start timing instrumentation (iOS/Android) |
enableFramesTracking | bool | true | Slow/frozen frame monitoring (iOS/Android/macOS) |
proguardUuid | string | — | Proguard UUID for Android obfuscation mapping |
Session & Release Health Options
| Option | Type | Default | Purpose |
|---|---|---|---|
enableAutoSessionTracking | bool | true | Session tracking for crash-free user/session metrics |
autoSessionTrackingInterval | Duration | 30s | Background inactivity before session ends |
Replay Options (options.replay)
| Option | Type | Default | Purpose |
|---|---|---|---|
replay.sessionSampleRate | double | 0.0 | Fraction of all sessions recorded |
replay.onErrorSampleRate | double | 0.0 | Fraction of error sessions recorded |
Replay Privacy Options (options.privacy)
| Option / Method | Default | Purpose |
|---|---|---|
privacy.maskAllText | true | Mask all text widget content |
privacy.maskAllImages | true | Mask all image widgets |
privacy.maskAssetImages | true | Mask images from root asset bundle |
privacy.mask<T>() | — | Mask a specific widget type and all subclasses |
privacy.unmask<T>() | — | Unmask a specific widget type |
privacy.maskCallback<T>() | — | Custom masking decision per widget instance |
HTTP Options
| Option | Type | Default | Purpose |
|---|---|---|---|
captureFailedRequests | bool | true (Flutter) | Auto-capture HTTP errors |
maxRequestBodySize | enum | never | Body capture: never, small, medium, always |
failedRequestStatusCodes | List | [500–599] | Status codes treated as failures |
failedRequestTargets | List | ['.*'] | URL patterns to monitor |
Hook Options
| Option | Type | Purpose |
|---|---|---|
beforeSend | (SentryEvent, Hint) → SentryEvent? | Modify or drop error events. Return null to drop |
beforeSendTransaction | (SentryEvent) → SentryEvent? | Modify or drop transaction events |
beforeBreadcrumb | (Breadcrumb, Hint) → Breadcrumb? | Process breadcrumbs before storage |
beforeSendLog | (SentryLog) → SentryLog? | Filter structured logs before sending |
Environment Variables
Pass via --dart-define at build time:
| Variable | Purpose | Notes |
|---|---|---|
SENTRY_DSN | Data Source Name | Falls back from options.dsn |
SENTRY_ENVIRONMENT | Deployment environment | Falls back from options.environment |
SENTRY_RELEASE | Release identifier | Falls back from options.release |
SENTRY_DIST | Build distribution | Falls back from options.dist |
SENTRY_AUTH_TOKEN | Upload debug symbols | Never embed in app — build tool only |
SENTRY_ORG | Organization slug | Used by sentry_dart_plugin |
SENTRY_PROJECT | Project slug | Used by sentry_dart_plugin |
Usage:
flutter build apk --release \
--dart-define=SENTRY_DSN=https://xxx@sentry.io/123 \
--dart-define=SENTRY_ENVIRONMENT=productionThen in code:
options.dsn = const String.fromEnvironment('SENTRY_DSN');
options.environment = const String.fromEnvironment('SENTRY_ENVIRONMENT', defaultValue: 'development');Production Settings
Lower sample rates and harden config before shipping:
Future<void> main() async {
final isProduction = const bool.fromEnvironment('dart.vm.product');
await SentryFlutter.init(
(options) {
options.dsn = const String.fromEnvironment('SENTRY_DSN');
options.environment = isProduction ? 'production' : 'development';
// Trace 10% of transactions in high-traffic production
options.tracesSampleRate = isProduction ? 0.1 : 1.0;
// Profile 100% of traced transactions (profiling is always a subset)
options.profilesSampleRate = 1.0;
// Replay all error sessions, sample 5% of normal sessions
options.replay.onErrorSampleRate = 1.0;
options.replay.sessionSampleRate = isProduction ? 0.05 : 1.0;
// Disable debug logging in production
options.debug = !isProduction;
},
appRunner: () => runApp(SentryWidget(child: MyApp())),
);
}Default Auto-Enabled Integrations
These are active with no extra config when you call SentryFlutter.init():
| Integration | What it does |
|---|---|
FlutterErrorIntegration | Captures FlutterError.onError framework errors |
RunZonedGuardedIntegration | Catches unhandled Dart exceptions in runZonedGuarded |
NativeAppStartIntegration | App start timing (iOS/Android) |
FramesTrackingIntegration | Slow/frozen frames (iOS/Android/macOS) |
NativeUserInteractionIntegration | User interaction breadcrumbs from native layer |
UserInteractionIntegration | Dart-layer tap/click transactions (requires SentryWidget) |
DeviceContextIntegration | Device model, OS version, screen resolution |
AppContextIntegration | App version, build number, bundle ID |
ConnectivityIntegration | Network connectivity change breadcrumbs |
HttpClientIntegration | Auto-instrument Dart http requests |
SdkIntegration | SDK metadata tagging |
ReleaseIntegration | Auto-set release on iOS/Android from package info |
---
Verification
After setup, test that Sentry is receiving events:
// Add a test button somewhere visible during development:
ElevatedButton(
onPressed: () {
throw Exception('Sentry test error!');
},
child: const Text('Test Sentry Error'),
)
// Or capture manually:
ElevatedButton(
onPressed: () {
Sentry.captureMessage('Sentry test message', level: SentryLevel.info);
},
child: const Text('Test Sentry Message'),
)
// Test structured logging:
ElevatedButton(
onPressed: () {
Sentry.logger.info('Test log from Flutter app');
},
child: const Text('Test Sentry Log'),
)Check the Sentry dashboard:
- Issues → test error should appear within seconds
- Traces → look for a navigation transaction with child spans
- Replays → session recording visible after app interaction (iOS/Android only)
- Logs → structured log entries if
enableLogs: true
⚠️ Platform limitations in debug mode:
- Native crashes, session replay, slow/frozen frames, and app start metrics only fully work in release builds on iOS/Android
- Run flutter run --release or use a real device/emulator to test native features- Debug mode uses the Dart VM with JIT compilation — some native integrations behave differently
---
Phase 4: Cross-Link
After completing Flutter setup, check for a backend missing Sentry coverage:
# Adjacent backend directories
ls ../backend ../server ../api 2>/dev/null
cat ../go.mod 2>/dev/null | head -3
cat ../requirements.txt ../pyproject.toml 2>/dev/null | head -3
cat ../Gemfile 2>/dev/null | head -3
ls ../backend/package.json ../server/package.json 2>/dev/nullIf a backend exists without Sentry, suggest the matching skill:
| Detected | Suggest skill |
|---|---|
Go backend (go.mod) | sentry-go-sdk |
Python backend (requirements.txt, pyproject.toml) | sentry-python-sdk |
Ruby backend (Gemfile) | sentry-ruby-sdk |
| Node.js backend | sentry-node-sdk |
.NET backend (*.csproj) | sentry-dotnet-sdk |
| React / Next.js web | sentry-react-sdk / sentry-nextjs-sdk |
Distributed tracing — if a backend skill is added, configure tracePropagationTargets in Flutter to propagate trace context to your API:
options.tracePropagationTargets = ['api.myapp.com', 'localhost'];
options.propagateTraceparent = true; // also send W3C traceparent headerThis links mobile transactions to backend traces in the Sentry waterfall view.
---
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing in Sentry | Set options.debug = true — SDK logs to Flutter console; verify DSN is correct |
SentryFlutter.init throws | Ensure main() is async and you await SentryFlutter.init(...) |
| Stack traces unreadable in Sentry | Upload debug symbols with sentry_dart_plugin; build with --obfuscate --split-debug-info |
| Stack traces missing on Web | Build with --source-maps and run dart run sentry_dart_plugin to upload |
| Native crashes not captured | Confirm enableNativeCrashHandling: true; test in release mode, not debug |
| Session replay not recording | iOS/Android only; confirm SentryWidget wraps root; check replay.onErrorSampleRate |
| Replay shows blank screens | Confirm SentryWidget(child: MyApp()) is outermost widget; not inside navigator |
| Profiling not working | iOS and macOS only (alpha); confirm tracesSampleRate > 0 is set first |
| Navigation not tracked | Add SentryNavigatorObserver() to navigatorObservers; name all routes |
| GoRouter routes unnamed | Add name: to all GoRoute entries — unnamed routes are tracked as null |
| TTFD never reports | Call SentryFlutter.currentDisplay()?.reportFullyDisplayed() after data loads, or wrap with SentryDisplayWidget |
sentry_dart_plugin auth error | Set SENTRY_AUTH_TOKEN env var instead of hardcoding in pubspec.yaml |
| Android ProGuard mapping missing | Ensure --extra-gen-snapshot-options=--save-obfuscation-map=... flag is set |
| iOS dSYM not uploaded | sentry_dart_plugin handles this; check upload_debug_symbols: true in pubspec.yaml sentry: block |
pub get fails: Dart SDK too old | sentry_flutter ≥9.0.0 requires Dart ≥3.5.0; run flutter upgrade |
| Hot restart crashes on Android debug | Known issue (fixed in SDK ≥9.9.0); upgrade if on older version |
| ANR detection too aggressive | Increase anrTimeoutInterval (default: 5000ms) |
| Too many transactions in dashboard | Lower tracesSampleRate to 0.1 or use tracesSampler to drop health checks |
beforeSend not firing for native crashes | Expected — beforeSend intercepts only Dart-layer events; native crashes bypass it |
| Crons not available | The Flutter/Dart SDK does not support Sentry Crons; use a server-side SDK instead |
SentryWidget warning in tests | Wrap test widget with SentryFlutter.init() in setUpAll, or use enabled: false |
| Firebase Remote Config: Linux/Windows | sentry_firebase_remote_config not supported on Linux/Windows (Firebase limitation) |
| Isar tracing on Web | sentry_isar does NOT support Web (Isar does not support Web) |
Ecosystem Integrations — Sentry Flutter SDK
Add these packages alongside sentry_flutter for deeper instrumentation:
HTTP Clients
Standard `http` package — built into sentry_flutter, no extra install:
import 'package:sentry/sentry.dart';
// Wrap your http client
final client = SentryHttpClient(
captureFailedRequests: true,
failedRequestStatusCodes: [SentryStatusCode.range(400, 599)],
);
try {
final response = await client.get(Uri.parse('https://api.example.com/users'));
} finally {
client.close();
}Dio — install sentry_dio:
flutter pub add sentry_dioimport 'package:dio/dio.dart';
import 'package:sentry_dio/sentry_dio.dart';
final dio = Dio();
// Add your interceptors first, THEN addSentry() last
dio.addSentry(
captureFailedRequests: true,
failedRequestStatusCodes: [SentryStatusCode.range(400, 599)],
);Databases
| Package | Install | Setup |
|---|---|---|
sentry_sqflite | flutter pub add sentry_sqflite | databaseFactory = SentrySqfliteDatabaseFactory(); |
sentry_drift | flutter pub add sentry_drift | .interceptWith(SentryQueryInterceptor(databaseName: 'db')) |
sentry_hive | flutter pub add sentry_hive | Use SentryHive instead of Hive |
sentry_isar | flutter pub add sentry_isar | Use SentryIsar.open() instead of Isar.open() |
Other
| Package | Install | Purpose |
|---|---|---|
sentry_logging | flutter pub add sentry_logging | Dart logging package → Sentry breadcrumbs/events |
sentry_link | flutter pub add sentry_link | GraphQL (gql, graphql_flutter, ferry) tracing |
sentry_supabase | flutter pub add sentry_supabase | Supabase query tracing (SDK ≥9.9.0) |
sentry_firebase_remote_config | flutter pub add sentry_firebase_remote_config | Feature flag tracking |
sentry_file | flutter pub add sentry_file | File I/O tracing via .sentryTrace() extension |
State Management Patterns
No official packages — wire Sentry via observer APIs:
| Framework | Hook point | Pattern |
|---|---|---|
| BLoC/Cubit | BlocObserver.onError | Sentry.captureException(error, stackTrace: stackTrace) inside onError; set Bloc.observer = SentryBlocObserver() before init |
| Riverpod | ProviderObserver.providerDidFail | Fires for FutureProvider/StreamProvider failures; wrap app with ProviderScope(observers: [SentryProviderObserver()]) |
| Provider/ChangeNotifier | try/catch in notifyListeners callers | Manually call Sentry.captureException(e, stackTrace: stack) in catch blocks |
| GetX | GetMaterialApp.onError | GetMaterialApp(onError: (details) => Sentry.captureException(...)) |
Error Monitoring — Flutter SDK Reference
Minimum SDK version: sentry_flutter ≥ 7.0.0 (all features), ≥ 9.14.0 (tombstone, trace sync)Platforms: All (Android, iOS, macOS, Linux, Windows, Web — platform-specific caveats noted)
What's Auto-Captured
When SentryFlutter.init() runs, the SDK installs these handlers automatically:
| Handler | Captures | Platforms |
|---|---|---|
FlutterError.onError | Widget build errors, rendering errors, gesture errors | All |
PlatformDispatcher.instance.onError | Uncaught Dart async errors (root zone) | All (Flutter ≥ 3.3) |
| Android SDK (bundled) | Java/Kotlin JVM exceptions, NDK native crashes | Android |
| iOS/macOS SDK (bundled) | ObjC NSException, Swift errors, POSIX signals, Mach exceptions | iOS, macOS |
WidgetsBindingObserver | App lifecycle breadcrumbs | Linux, Windows, Web (no native SDK) |
Silent framework errors (FlutterErrorDetails.silent == true) are excluded unless you opt in:
options.reportSilentFlutterErrors = true;---
Manual Capture APIs
Capture Exception
Always pass stackTrace — without it Sentry can't show the correct call stack:
import 'package:sentry/sentry.dart';
try {
await riskyOperation();
} catch (e, stackTrace) {
await Sentry.captureException(e, stackTrace: stackTrace);
}With a one-off local scope (doesn't affect subsequent events):
await Sentry.captureException(
e,
stackTrace: stackTrace,
withScope: (scope) {
scope.setTag('feature', 'checkout');
scope.setTag('step', 'payment');
scope.level = SentryLevel.fatal;
scope.setExtra('orderId', orderId);
},
);Capture Message
await Sentry.captureMessage(
'Rate limit threshold exceeded',
level: SentryLevel.warning,
);
// Available levels:
// SentryLevel.debug | info | warning | error | fatalCapture User Feedback
final eventId = await Sentry.captureException(e, stackTrace: st);
await Sentry.captureFeedback(
SentryFeedback(
message: 'App froze after uploading the photo.',
contactEmail: 'user@example.com',
name: 'Jane Doe',
associatedEventId: eventId,
),
);---
Scope — Enrich All Events
Global Scope (persists across events)
import 'package:sentry/sentry.dart';
// Set user on login
Sentry.configureScope((scope) => scope.setUser(SentryUser(
id: 'user-123',
username: 'jdoe',
email: 'jane@example.com',
name: 'Jane Doe',
data: {'subscription_tier': 'pro'},
)));
// Clear user on logout
Sentry.configureScope((scope) => scope.setUser(null));
// Tags — indexed, searchable (key max 32 chars, value max 200 chars)
Sentry.configureScope((scope) {
scope.setTag('app.flavor', 'enterprise');
scope.setTag('locale', 'pt-BR');
});
// Contexts — non-searchable structured data (visible in Sentry UI)
Sentry.configureScope((scope) => scope.setContexts('cart', {
'items_count': 3,
'total_usd': 149.99,
}));
// Attributes (SDK ≥9.9.0) — apply to logs, metrics, and spans too
Sentry.setAttributes({'user_tier': SentryAttribute.string('premium')});
Sentry.removeAttribute('user_tier');Scope Sync to Native (Android/iOS)
When options.enableScopeSync = true (default), these methods sync to the native SDK layer so native crash reports include your Dart context:
scope.setUser()/scope.setContexts()/scope.setTag()/scope.setExtra()scope.addBreadcrumb()/scope.clearBreadcrumbs()
---
Breadcrumbs
Manual breadcrumbs build an audit trail leading to each error:
Sentry.addBreadcrumb(Breadcrumb(
message: 'User tapped checkout button',
category: 'ui.action',
type: 'user',
level: SentryLevel.info,
data: {'screen': 'CartScreen', 'cart_total': 149.99},
));Filter or modify breadcrumbs before storage:
options.beforeBreadcrumb = (breadcrumb, hint) {
// Drop noisy analytics calls
if (breadcrumb.data?['url']?.contains('analytics') == true) {
return null; // null = drop
}
// Strip auth headers from HTTP breadcrumbs
if (breadcrumb.type == 'http') {
final data = Map<String, dynamic>.from(breadcrumb.data ?? {})
..remove('Authorization');
return breadcrumb.copyWith(data: data);
}
return breadcrumb;
};Increase capacity if needed (default: 100):
options.maxBreadcrumbs = 150;---
Event Filtering
Drop or modify events before they're sent:
options.beforeSend = (event, hint) async {
// Drop database connection errors (too noisy)
if (event.throwable is DatabaseConnectionException) return null;
// Group similar payment failures
if (event.throwable is PaymentException) {
event.fingerprint = ['payment-failure'];
}
// Strip server name for privacy
event.serverName = '';
return event;
};⚠️ beforeSend intercepts Dart-layer events only. Native crashes from Android NDK or iOS bypass it.Error sampling (drop a fraction of errors — not recommended unless volume is very high):
options.sampleRate = 0.5; // send only 50% of errors---
Isolate Error Capture
Errors in non-root Dart isolates aren't automatically captured. Forward them explicitly:
import 'dart:isolate';
import 'package:sentry/sentry.dart';
final isolate = await Isolate.spawn(myIsolateEntry, someData);
isolate.addSentryErrorListener(); // forwards uncaught errors to Sentry⚠️ Isolate errors are NOT captured on Web (no Isolate API in browser).
---
Attachments
Automatically attach screenshots and widget hierarchy to error events:
options.attachScreenshot = true; // screenshot at time of error
options.screenshotQuality = ScreenshotQuality.high; // full/high/medium/low
options.attachViewHierarchy = true; // JSON widget tree snapshotRequires SentryWidget(child: MyApp()) as the app root. Not available on Web.
Attach files manually via scope:
import 'package:sentry/sentry_io.dart';
final attachment = IoSentryAttachment.fromPath('/path/to/debug.log');
Sentry.configureScope((scope) => scope.addAttachment(attachment));---
Android Native Crash Options
// Android 12+ tombstone crash info via ApplicationExitInfo (SDK ≥9.14.0, opt-in)
options.enableTombstone = true;
// Sync Java/Kotlin scope to NDK
options.enableNdkScopeSync = true; // default: true
// Attach all threads to crash report
options.attachThreads = true; // default: false
// ANR detection
options.anrEnabled = true; // default: true
options.anrTimeoutInterval = 5000; // ms; increase if too many false positives---
iOS/macOS Native Crash Options
// Watchdog termination (OOM kill) tracking
options.enableWatchdogTerminationTracking = true; // default: true
// Native HTTP failures independent of Dart client (SDK ≥9.11.0)
options.captureNativeFailedRequests = true;---
Release Health
Session tracking is on by default — gives you crash-free user and session metrics in the Sentry dashboard:
// Disable if not needed
options.enableAutoSessionTracking = false;
// Tune session expiry (how long in background before a new session starts)
options.autoSessionTrackingInterval = const Duration(seconds: 60); // default: 30sRelease is auto-set on iOS/Android as "packageName@versionName+versionCode". Override if needed:
options.release = 'my-app@2.1.0+105';---
Troubleshooting
| Issue | Solution |
|---|---|
| Events not appearing in Sentry | Set options.debug = true, check Flutter console for SDK errors; verify DSN |
| Stack traces unreadable | Build with --obfuscate --split-debug-info and upload with sentry_dart_plugin |
| Native crashes not captured | Confirm enableNativeCrashHandling: true; test in release mode (not debug) |
beforeSend not firing for native crashes | Expected — beforeSend only intercepts Dart-layer events |
| Silent Flutter errors missed | Set options.reportSilentFlutterErrors = true |
| Isolate errors not captured | Call isolate.addSentryErrorListener() on each spawned isolate |
| Attachments missing from events | Confirm SentryWidget(child: MyApp()) is the root widget |
| ANR false positives on slow devices | Increase anrTimeoutInterval above 5000ms |
| Events missing user/tag context | Set context before error occurs via Sentry.configureScope(); native crashes read scope at crash time |
| Too many events in dashboard | Lower sampleRate (last resort) or use beforeSend to drop low-value errors |
Logging — Sentry Flutter SDK
Minimum SDK:sentry_flutter≥ 9.5.0 for structured logs (enableLogs)
`sentry_logging` integration:sentry_logging≥ 9.5.0 + Dartloggingpackage ≥ 1.0.0
`enableLogs` flag: off by default — must be explicitly enabled
Flutter/Dart has two complementary logging paths: 1. Sentry structured logs — Sentry.logger.* API (direct) or via sentry_logging integration (bridges the Dart logging package) 2. Breadcrumbs — Automatic breadcrumbs from sentry_logging for navigation and debug events
---
Table of Contents
1. Enabling Logs 2. Direct Logger API 3. sentry_logging Integration (Dart logging package) 4. Structured Attributes 5. Filtering with beforeSendLog 6. Log Correlation with Traces 7. Configuration Reference 8. Known Limitations 9. Troubleshooting
---
1. Enabling Logs
enableLogs is off by default — opt in explicitly:
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
options.enableLogs = true; // required — logs are disabled unless this is set
},
appRunner: () => runApp(MyApp()),
);
}---
2. Direct Logger API
Use Sentry.logger to emit structured logs at six severity levels:
import 'package:sentry/sentry.dart';
// Fine-grained debugging — high volume, filter in production
Sentry.logger.trace('Starting checkout flow', {'step': 'init'});
// Development diagnostics
Sentry.logger.debug('Cache lookup', {'key': 'user:123', 'hit': false});
// Normal operations and business milestones
Sentry.logger.info('Order created', {'orderId': 'order_456', 'total': 99.99});
// Degraded state, approaching limits
Sentry.logger.warn('Rate limit approaching', {
'endpoint': '/api/search/',
'current': 95,
'max': 100,
});
// Failures requiring attention
Sentry.logger.error('Payment failed', {
'reason': 'card_declined',
'userId': 'u_1',
});
// Critical failures — app or subsystem is down
Sentry.logger.fatal('Database unavailable', {'host': 'db-primary'});Level selection guide
| Level | When to use |
|---|---|
trace | Step-by-step internals, loop iterations, low-level flow tracking |
debug | Diagnostic info useful during development |
info | Business events, user actions, meaningful state transitions |
warn | Recoverable errors, degraded performance, approaching limits |
error | Failures that need investigation but don't crash the app |
fatal | Unrecoverable failures — app or critical subsystem is down |
Attribute value types: String, int, double, and bool only. Other types will be dropped or coerced.
---
3. sentry_logging Integration (Dart logging package)
The sentry_logging package bridges the standard Dart logging package to Sentry. This is ideal for projects already using logging for structured output.
Installation
# pubspec.yaml
dependencies:
sentry_flutter: ^9.14.0
sentry_logging: ^9.14.0
logging: ^1.0.0Configuration
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_logging/sentry_logging.dart';
import 'package:logging/logging.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
options.enableLogs = true;
options.addIntegration(
LoggingIntegration(
// Minimum level that creates breadcrumbs (default: Level.INFO)
minBreadcrumbLevel: Level.INFO,
// Minimum level that creates Sentry error events (default: Level.SEVERE)
minEventLevel: Level.SEVERE,
// Minimum level that creates structured logs (default: Level.INFO)
minSentryLogLevel: Level.INFO,
),
);
},
appRunner: () => runApp(MyApp()),
);
}Usage with the logging package
import 'package:logging/logging.dart';
final _logger = Logger('PaymentService');
class PaymentService {
Future<void> processPayment(String orderId, double amount) async {
_logger.info('Processing payment', {'orderId': orderId, 'amount': amount});
try {
final result = await _chargeCard(amount);
_logger.info('Payment succeeded', {
'orderId': orderId,
'transactionId': result.transactionId,
});
} catch (e, stackTrace) {
// Pass error and stackTrace for accurate reporting
_logger.severe('Payment failed', e, stackTrace);
}
}
}Stack trace handling
The Dart logging package does not automatically capture stack traces. Configure it explicitly:
// Option 1 — automatic stack trace capture at SEVERE and above
Logger.root.recordStackTraceAtLevel = Level.SEVERE;
// Option 2 — pass stack trace manually to each log call
Logger('MyService').severe('Error occurred', error, stackTrace);What each log level produces
| Dart Level | ≥ minBreadcrumbLevel | ≥ minSentryLogLevel | ≥ minEventLevel |
|---|---|---|---|
Level.FINEST/FINER/FINE | Breadcrumb (if configured) | Structured log | — |
Level.INFO/CONFIG | ✅ Breadcrumb | ✅ Structured log | — |
Level.WARNING | ✅ Breadcrumb | ✅ Structured log | — |
Level.SEVERE | ✅ Breadcrumb | ✅ Structured log | ✅ Error event |
Level.SHOUT | ✅ Breadcrumb | ✅ Structured log | ✅ Error event |
---
4. Structured Attributes
Attributes passed to Sentry.logger.* become queryable columns in the Sentry Logs UI:
Sentry.logger.info('Checkout completed', {
'orderId': order.id,
'userId': user.id,
'cartValue': cart.total,
'itemCount': cart.items.length,
'paymentMethod': 'stripe',
'durationMs': DateTime.now().difference(startTime).inMilliseconds,
});
Sentry.logger.error('Navigation failed', {
'fromRoute': '/home',
'toRoute': '/profile',
'errorCode': err.code,
'retryable': true,
});Scope-level attributes
Set attributes on the scope and they are automatically attached to all logs emitted while the scope is active:
// Global scope — set once at app startup
Sentry.configureScope((scope) {
scope.setContexts('app', {
'version': '2.1.0',
'build': '42',
'flavor': 'production',
});
});
// Per-operation scope — configure the current scope before logging
Sentry.configureScope((scope) {
scope.setTag('orderId', 'ord_789');
scope.setTag('paymentMethod', 'stripe');
});
Sentry.logger.info('Validating cart', {'cartId': cart.id});
await processPayment();
Sentry.logger.info('Payment complete');
// Both logs above carry orderId and paymentMethod tags
});Auto-attached attributes
The SDK automatically attaches these to every log:
| Attribute | Source |
|---|---|
sentry.environment | options.environment |
sentry.release | options.release |
sentry.sdk.name / sentry.sdk.version | SDK internals |
user.id, user.email | Sentry.setUser() when set |
origin | Identifies which integration emitted the log |
---
5. Filtering with beforeSendLog
Filter or mutate every log before it is transmitted. Return null to drop the log entirely:
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
options.enableLogs = true;
options.beforeSendLog = (log) {
// Drop trace/debug in production to reduce volume
if (log.level == SentryLogLevel.trace ||
log.level == SentryLogLevel.debug) {
return null;
}
// Scrub sensitive attribute values
if (log.attributes.containsKey('password')) {
log.attributes.remove('password');
}
if (log.attributes.containsKey('creditCard')) {
log.attributes['creditCard'] = '[REDACTED]';
}
return log;
};
},
appRunner: () => runApp(MyApp()),
);---
6. Log Correlation with Traces
When tracing is enabled, logs emitted inside an active span are automatically correlated in the Sentry UI. You can navigate from a log to its parent transaction.
import 'package:sentry/sentry.dart';
Future<void> processOrder(String orderId) async {
final transaction = Sentry.startTransaction('process-order', 'task');
try {
Sentry.logger.info('Validating cart', {'orderId': orderId});
await validateCart(orderId);
Sentry.logger.info('Charging payment', {'orderId': orderId});
await chargePayment(orderId);
Sentry.logger.info('Confirming order', {'orderId': orderId});
await confirmOrder(orderId);
transaction.finish(status: const SpanStatus.ok());
} catch (e) {
transaction.finish(status: const SpanStatus.internalError());
rethrow;
}
// All three logs are linked to the process-order span in the trace view
}Enable tracing in SentryFlutter.init:
options.tracesSampleRate = 1.0; // required for log-to-trace correlation
options.enableLogs = true;---
7. Configuration Reference
SentryFlutter.init options
| Option | Type | Default | Description |
|---|---|---|---|
enableLogs | bool | false | Master switch — must be true for all structured logging |
beforeSendLog | SentryLog? Function(SentryLog) | null | Filter/mutate logs before transmission. Return null to drop. |
LoggingIntegration options
| Option | Type | Default | Description |
|---|---|---|---|
minBreadcrumbLevel | Level | Level.INFO | Minimum Dart log level to create Sentry breadcrumbs |
minEventLevel | Level | Level.SEVERE | Minimum Dart log level to create Sentry error events |
minSentryLogLevel | Level | Level.INFO | Minimum Dart log level to create Sentry structured logs |
Version requirements
| Feature | Min Package | Min SDK Version |
|---|---|---|
enableLogs / structured logs | sentry_flutter | 9.5.0 |
sentry_logging integration | sentry_logging | 9.5.0 |
beforeSendLog hook | sentry_flutter | 9.5.0 |
---
8. Known Limitations
| Limitation | Details |
|---|---|
| Crash buffer loss | Logs buffered since last flush are lost on unexpected termination before the buffer is sent |
| No per-log sampling | Use beforeSendLog to reduce volume — sampling is all-or-nothing |
| Dart logging package stack traces | Must be enabled manually via Logger.root.recordStackTraceAtLevel or passed explicitly |
sentry_logging is separate package | Must be added to pubspec.yaml separately — not bundled with sentry_flutter |
enableLogs is off by default | Logs are silently discarded if enableLogs is not true |
---
9. Troubleshooting
| Issue | Solution |
|---|---|
| Logs not appearing in Sentry | Verify enableLogs: true is set in SentryFlutter.init() |
Sentry.logger not available | Import package:sentry/sentry.dart; check sentry_flutter ≥ 9.5.0 |
LoggingIntegration type not found | Add sentry_logging to pubspec.yaml and import package:sentry_logging/sentry_logging.dart |
| Logs appear but no stack traces on errors | Set Logger.root.recordStackTraceAtLevel = Level.SEVERE or pass stackTrace manually |
Attribute values showing [Filtered] | Server-side PII scrubbing rule matched — adjust Data Scrubbing settings in your Sentry project |
| Logs not linked to traces | Enable tracing (tracesSampleRate > 0) and emit logs inside an active transaction (Sentry.startTransaction()) |
| Too many logs in production | Use beforeSendLog to drop trace/debug levels |
sentry_logging logs not forwarded as structured logs | Check that minSentryLogLevel is set to the expected level and enableLogs: true |
| Logs disappearing silently | Check your Sentry org stats for rate limiting; verify log payload < 1 MB |
Metrics — Sentry Flutter SDK
Minimum SDK: sentry_flutter ≥ 9.11.0 for trace-connected metricsSupported metric types: Counter, Distribution, Gauge (no set type)Per-metric key limit: 2 KB
Metrics let you track quantitative data about your app's behavior — things like button tap counts, API latency distributions, or active session gauges. Unlike error events, metrics are aggregated before being sent to Sentry.
---
Table of Contents
1. Metric Types Overview 2. Counter 3. Distribution 4. Gauge 5. Tags and Trace Correlation 6. Configuration Reference 7. Known Limitations 8. Troubleshooting
---
1. Metric Types Overview
| Type | Method | Use Case | Example |
|---|---|---|---|
| Counter | Sentry.metrics.increment() | Count occurrences | Button taps, API calls, errors |
| Distribution | Sentry.metrics.distribution() | Measure value distributions | API response times, image sizes |
| Gauge | Sentry.metrics.gauge() | Track min/max/avg of a value | Active sessions, queue depth |
| ~~Set~~ | ~~Sentry.metrics.set()~~ | ~~Count unique occurrences~~ | Not supported in Flutter SDK |
Note on `set` metrics: The set type is not available in the Sentry Flutter/Dart SDK. Use a counter or external unique-value tracking if you need to count distinct users or IDs.---
2. Counter
Counters track how many times something happens. Use increment() — each call adds to the running total.
import 'package:sentry/sentry.dart';
// Simple increment (adds 1)
Sentry.metrics.increment('button.tapped');
// Custom increment value
Sentry.metrics.increment('orders.processed', value: 5.0);
// With tags for segmentation
Sentry.metrics.increment(
'api.request',
value: 1.0,
unit: SentryMeasurementUnit.none,
tags: {
'endpoint': '/api/search',
'method': 'GET',
'status': '200',
},
);Practical examples
// Track feature usage
void onShareButtonTapped() {
Sentry.metrics.increment('feature.share.tapped');
// existing share logic...
}
// Count errors by type
void onPaymentError(String errorType) {
Sentry.metrics.increment(
'payment.error',
tags: {'type': errorType},
);
}
// Track A/B test variant interactions
void onExperimentAction(String variant, String action) {
Sentry.metrics.increment(
'experiment.action',
tags: {
'variant': variant,
'action': action,
},
);
}---
3. Distribution
Distributions capture the spread of values over time — min, max, sum, count, and percentiles (p50, p75, p95, p99). Ideal for latency and size measurements.
import 'package:sentry/sentry.dart';
// API response time in milliseconds
final stopwatch = Stopwatch()..start();
await fetchUserData(userId);
stopwatch.stop();
Sentry.metrics.distribution(
'api.response_time',
value: stopwatch.elapsedMilliseconds.toDouble(),
unit: SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.milliSecond),
tags: {'endpoint': '/users', 'cached': 'false'},
);Unit types
// Duration
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.nanoSecond)
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.microSecond)
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.milliSecond)
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.second)
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.minute)
SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.hour)
// Data sizes
SentryMeasurementUnit.information(InformationSentryMeasurementUnit.byte)
SentryMeasurementUnit.information(InformationSentryMeasurementUnit.kilobyte)
SentryMeasurementUnit.information(InformationSentryMeasurementUnit.megabyte)
SentryMeasurementUnit.information(InformationSentryMeasurementUnit.gigabyte)
// Fractions
SentryMeasurementUnit.fraction(FractionSentryMeasurementUnit.ratio)
SentryMeasurementUnit.fraction(FractionSentryMeasurementUnit.percent)
// Custom / unitless
SentryMeasurementUnit.none
SentryMeasurementUnit.custom('items')Practical examples
// Image load time
Future<void> loadImage(String url) async {
final start = DateTime.now();
await precacheImage(NetworkImage(url), context);
final elapsed = DateTime.now().difference(start);
Sentry.metrics.distribution(
'image.load_time',
value: elapsed.inMilliseconds.toDouble(),
unit: SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.milliSecond),
tags: {'cached': 'false'},
);
}
// Payload size tracking
void onApiResponse(http.Response response) {
Sentry.metrics.distribution(
'api.response_size',
value: response.contentLength?.toDouble() ?? 0,
unit: SentryMeasurementUnit.information(InformationSentryMeasurementUnit.byte),
tags: {'endpoint': response.request?.url.path ?? 'unknown'},
);
}
// Cart value distribution
void onCheckoutCompleted(double cartTotal) {
Sentry.metrics.distribution(
'checkout.cart_value',
value: cartTotal,
unit: SentryMeasurementUnit.none,
);
}---
4. Gauge
Gauges track the statistical properties (last, min, max, sum, count) of a set of values emitted during the aggregation window. Use for things that have a meaningful current state.
import 'package:sentry/sentry.dart';
// Track active background operations
Sentry.metrics.gauge(
'background.tasks.active',
value: activeTaskCount.toDouble(),
);
// Track session depth
Sentry.metrics.gauge(
'navigation.stack_depth',
value: Navigator.of(context).canPop() ? stackDepth.toDouble() : 0,
);
// Monitor cache entries
Sentry.metrics.gauge(
'cache.entry_count',
value: imageCache.currentSize.toDouble(),
tags: {'type': 'image_cache'},
);---
5. Tags and Trace Correlation
Tags
All metric types accept a tags map. Tags are key-value strings used to filter and group metrics in the Sentry Metrics UI:
Sentry.metrics.increment(
'checkout.step',
tags: {
'step': 'payment', // string values only
'platform': 'ios',
'user_type': 'subscriber',
},
);Trace correlation
As of sentry_flutter 9.11.0, metrics are automatically linked to the active trace when emitted inside a span. This allows you to view metric data alongside transaction performance in the Sentry UI:
final transaction = Sentry.startTransaction('checkout', 'ui.action');
try {
// This increment is linked to the checkout transaction
Sentry.metrics.increment('checkout.started');
final start = DateTime.now();
await processCheckout();
final elapsed = DateTime.now().difference(start);
Sentry.metrics.distribution(
'checkout.duration',
value: elapsed.inMilliseconds.toDouble(),
unit: SentryMeasurementUnit.duration(DurationSentryMeasurementUnit.milliSecond),
);
Sentry.metrics.increment('checkout.completed');
transaction.finish(status: const SpanStatus.ok());
} catch (e) {
transaction.finish(status: const SpanStatus.internalError());
rethrow;
}---
6. Configuration Reference
No special configuration is required for metrics beyond initializing the SDK with a valid DSN. Metrics are enabled by default.
| Method | Signature |
|---|---|
Sentry.metrics.increment(key, {value, unit, tags}) | Increment a counter |
Sentry.metrics.distribution(key, {value, unit, tags}) | Record a distribution value |
Sentry.metrics.gauge(key, {value, unit, tags}) | Record a gauge observation |
Version requirements
| Feature | Min SDK |
|---|---|
Sentry.metrics.* basic API | 9.0.0 |
| Trace-connected metrics | 9.11.0 |
---
7. Known Limitations
| Limitation | Details |
|---|---|
No set type | Sentry.metrics.set() is not supported in the Flutter/Dart SDK. Use counters or external tracking for unique-value counting. |
| 2 KB per-metric key limit | The metric key name + all tag key-value pairs must fit within 2 KB total. |
| Active development | The metrics API is stable but under active development. Some edge cases may change in future minor versions. |
| Aggregation window | Metrics are aggregated locally before being flushed. Individual data points are not preserved — you see aggregations (sum, count, min, max). |
| No sampling for metrics | Unlike errors and transactions, there is no sample rate for metrics — all emitted metrics are sent. Use tags and filtering instead of emitting fewer data points. |
| Tag values must be strings | Passing non-string tag values will be coerced or dropped. |
---
8. Troubleshooting
| Issue | Solution |
|---|---|
| Metrics not appearing in Sentry | Verify DSN is correct and the SDK is initialized. Check Sentry → Metrics to ensure the feature is enabled for your organization. |
SDK version doesn't have Sentry.metrics | Upgrade sentry_flutter to ≥ 9.0.0 |
| Metrics not linked to traces | Upgrade to sentry_flutter ≥ 9.11.0 and emit metrics inside an active transaction (Sentry.startTransaction()) |
set metric type not available | Expected — not supported in Flutter SDK. Use increment with a counter instead. |
| Metrics appear intermittently | Expected — metrics are batched and flushed on a schedule. Low-volume metrics may appear in Sentry with a delay. |
| Tags not showing up correctly | Verify all tag values are strings; check total metric key + tags size is under 2 KB |
Profiling — Sentry Flutter SDK
Minimum SDK: sentry_flutter ≥ 7.12.0Status: ⚠️ Alpha — API may change without a major version bump
Platforms: iOS and macOS only — Android, Web, Linux, Windows not supported
Profiling samples the Dart and native call stack at regular intervals to surface hot code paths and slow functions. It requires tracing to be enabled — only transactions that are sampled can be profiled.
---
Table of Contents
1. How Profiling Works 2. Basic Setup 3. What Data Is Captured 4. Performance Overhead 5. Configuration Reference 6. Known Limitations 7. Troubleshooting
---
1. How Profiling Works
When a transaction is sampled for profiling, the SDK starts sampling the call stack at a fixed interval for the duration of that transaction. The profile is attached to the transaction and uploaded to Sentry alongside it.
Sampling relationship
profilesSampleRate is relative to `tracesSampleRate`, not to all transactions:
All transactions
└── × tracesSampleRate → Traced transactions
└── × profilesSampleRate → Profiled transactionsExample: tracesSampleRate: 0.2 + profilesSampleRate: 0.5 → 10% of all transactions are profiled.
Platform constraint
The Flutter profiler hooks into the native iOS/macOS profiling infrastructure (equivalent to Instruments-style profiling). Android, Web, Linux, and Windows do not support profiling at the profilesSampleRate level — setting the option on those platforms has no effect.
---
2. Basic Setup
Minimum configuration
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
// Tracing must be enabled — profiling only applies to traced transactions
options.tracesSampleRate = 1.0;
// profilesSampleRate is relative to tracesSampleRate
// 1.0 = profile every traced transaction (development / testing only)
options.profilesSampleRate = 1.0;
},
appRunner: () => runApp(MyApp()),
);
}Recommended production configuration
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
// Trace 20% of transactions
options.tracesSampleRate = 0.2;
// Profile 50% of those → 10% of all transactions profiled
options.profilesSampleRate = 0.5;
},
appRunner: () => runApp(MyApp()),
);Production guidance: Profiling adds overhead. Keep profilesSampleRate low in production. Values above 0.1 are not recommended without explicit performance validation on your minimum supported device.Verifying profiles appear in Sentry
1. Build and run on a real iOS device (not Simulator — see Known Limitations) 2. Trigger a user interaction that creates a transaction (e.g., navigate between screens with SentryNavigatorObserver) 3. Open Sentry → Performance → find the transaction → look for the Profiling tab in the transaction detail
---
3. What Data Is Captured
In a profile
| Data | Description |
|---|---|
| Call stack samples | Sampled Dart + native stack frames at regular intervals |
| Flame graph | Aggregated view of time spent in each function |
| Timeline | Stack samples over time, correlated with transaction spans |
| Thread info | Main isolate thread, background threads, native threads |
| Function names | From Dart debug info + native debug symbols |
What profiles are linked to
Each profile is attached to the transaction that triggered it. In the Sentry UI you can:
- View the flame graph alongside the transaction's span waterfall
- Identify which functions were executing during slow spans
What is NOT captured
- Memory allocations (use Xcode Instruments for that)
- Network traffic details (captured separately via tracing spans)
- UI rendering frames (slow/frozen frames are a separate tracing metric via
SentryNavigatorObserver)
---
4. Performance Overhead
Profiling adds CPU overhead during sampled transactions. The sampler uses a fixed-interval approach (not full instrumentation), which limits overhead but does not eliminate it.
| Factor | Impact |
|---|---|
profilesSampleRate: 1.0 | Higher — every traced transaction is profiled |
profilesSampleRate: 0.1 | Low — only 10% of traced transactions profiled |
| Real device (iPhone 12+) | Minimal visible impact |
| Older devices (iPhone 8 and below) | Measurable impact — validate before enabling |
| iOS Simulator | Works but results differ from device — don't use for benchmarking |
Recommendations:
- Use
profilesSampleRate: 1.0only in development/testing - In production, start at
profilesSampleRate: 0.05and increase if needed - Always validate on your minimum supported iOS device before shipping
---
5. Configuration Reference
SentryFlutter.init options
| Option | Type | Default | Description |
|---|---|---|---|
profilesSampleRate | double (0–1) | null | Fraction of traced transactions to also profile. Relative to tracesSampleRate. iOS/macOS only. |
tracesSampleRate | double (0–1) | null | Required for profiling. Fraction of transactions to trace. |
tracesSampler | function | null | Dynamic trace sampling. Takes precedence over tracesSampleRate when set. |
Minimum version table
| Feature | Min SDK | Platforms |
|---|---|---|
profilesSampleRate | 7.12.0 | iOS, macOS |
---
6. Known Limitations
- iOS and macOS only. Android, Web, Linux, and Windows are not supported. The option is silently ignored on unsupported platforms.
- Alpha status. The profiling API and behavior may change in future minor versions. Pin your SDK version if stability matters for a critical workflow.
- Simulator accuracy. iOS Simulator profiling does not reflect real device characteristics. Always validate performance conclusions on real hardware.
- Tracing required. Setting
profilesSampleRatewithout a non-zerotracesSampleRatehas no effect — no transactions are sampled, so no profiles can be generated. - Obfuscation. Dart obfuscated builds (common in release builds) will show obfuscated Dart frame names unless you upload Dart symbol files via
sentry_dart_plugin. Configure the plugin to upload.symbolsfiles on each release build. - No manual profiling API. You cannot start/stop a profile programmatically — profiling is transaction-scoped only and controlled entirely by
profilesSampleRate. - Background transactions. If a transaction completes while the app is backgrounded, the profile may be truncated or absent.
---
7. Troubleshooting
| Issue | Likely Cause | Solution |
|---|---|---|
| No profiles appearing in Sentry | profilesSampleRate not set, or tracesSampleRate is 0 or null | Set both to > 0. Verify DSN is correct and events appear in Sentry at all. |
| Option is set but no profiles on Android | Android is not supported | Expected — profiling is iOS/macOS only in the Flutter SDK. |
Dart frames show obfuscated names (e.g., _f, _g) | Release build without symbol upload | Configure sentry_dart_plugin to upload Dart symbol files (--obfuscate + --split-debug-info) |
| Native frames show as hex addresses | dSYM not uploaded | Configure the Sentry Xcode build phase to upload dSYMs, or use sentry_dart_plugin |
| Profiles appear only for some transactions | Expected behavior | profilesSampleRate controls the fraction. Increase if you want broader coverage. |
| Profile flame graph shows "unknown" functions | Missing both Dart symbols and dSYMs | Upload both via sentry_dart_plugin and the Xcode build phase |
| Profiling not working on iOS Simulator | Known limitation — Simulator profiling may not match device behavior | Validate on a real device. Simulator results are indicative only. |
| App performance degraded after enabling profiling | profilesSampleRate too high | Reduce profilesSampleRate; test on your minimum supported device |
| SDK crashes on startup with profiling enabled | SDK version < 7.12.0 | Upgrade sentry_flutter to ≥ 7.12.0 |
Session Replay — Sentry Flutter SDK
Minimum SDK: sentry_flutter ≥ 9.0.0Platforms: iOS and Android only — Web, macOS, Linux, Windows not supported
Frame rate: ~1 frame per second (screenshot-based capture, not DOM recording)
Flutter Session Replay captures a visual record of user sessions as a compressed sequence of screenshots, combined with breadcrumbs, traces, and error context. It works differently from web replay — understanding this prevents surprises.
---
Table of Contents
1. How Flutter Replay Differs from Web Replay 2. Basic Setup 3. Sample Rates 4. Privacy and Masking 5. What the Replay UI Shows 6. Session Lifecycle 7. Performance Overhead 8. Configuration Reference 9. Known Limitations 10. Troubleshooting
---
1. How Flutter Replay Differs from Web Replay
| Dimension | Web Session Replay | Flutter Session Replay |
|---|---|---|
| Recording method | DOM serialization (HTML/CSS snapshots) | Screenshot-based (widget tree snapshots) |
| Frame rate | Variable (mutation-driven) | ~1 frame per second |
| Text in replay | ✅ Selectable, searchable | ❌ Pixel-only — text is in screenshots |
| CSS inspection | ✅ Available | ❌ Not available |
| Privacy mechanism | CSS-based DOM masking | Widget-level pixel masking |
| Rage clicks | ✅ Detected | ❌ Not supported |
| Touch recording | Full pointer events | Tap breadcrumbs only |
| Scroll positions | ✅ Precise | ⚠️ Approximate (from screenshots) |
---
2. Basic Setup
Flutter Session Replay is built into sentry_flutter — no separate package is needed. Wrap your root widget with SentryWidget (required for widget-tree privacy masking):
import 'package:flutter/widgets.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
Future<void> main() async {
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
// Session replay sampling
options.replay.sessionSampleRate = 0.1; // 10% of all sessions recorded
options.replay.onErrorSampleRate = 1.0; // 100% when an error occurs
},
appRunner: () => runApp(
SentryWidget( // required — wraps the app for screenshot capture
child: MyApp(),
),
),
);
}During development: Use options.replay.sessionSampleRate = 1.0 so every session is recorded. Lower it before shipping to production.---
3. Sample Rates
replay.sessionSampleRate
Records the entire user session from SDK initialization / app foreground entry.
- Range:
0.0–1.0 - Captures everything from session start
replay.onErrorSampleRate
Only activates when an error occurs. The SDK maintains a rolling pre-error buffer and captures that buffer plus everything after the error.
- Range:
0.0–1.0 - Gives you context for what led up to the error
Recommended production values
| Strategy | sessionSampleRate | onErrorSampleRate |
|---|---|---|
| Errors-only (minimal overhead) | 0 | 1.0 |
| Balanced | 0.05 | 1.0 |
| High visibility | 0.1 | 1.0 |
---
4. Privacy and Masking
⚠️ Production warning: Always verify your masking config before enabling in production. The SDK masks aggressively by default, but any customizations require thorough testing with your actual app UI. If you discover unmasked PII, disable Session Replay until resolved.
Default behavior
The SDK aggressively masks all text, images, and user input by default:
| Widget type | Default behavior |
|---|---|
Text, RichText, EditableText | ✅ Masked by default |
Image (including asset images) | ✅ Masked by default |
TextFormField, TextField | ✅ Masked by default |
Masked areas are replaced with a filled block using a neutral color.
Privacy configuration options
Configure masking in SentryFlutter.init:
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_DSN';
options.replay.sessionSampleRate = 0.1;
options.replay.onErrorSampleRate = 1.0;
// Masking options — all true by default
options.privacy.maskAllText = true; // mask Text, RichText, EditableText
options.privacy.maskAllImages = true; // mask Image widgets
options.privacy.maskAssetImages = true; // mask asset bundle images
},
appRunner: () => runApp(SentryWidget(child: MyApp())),
);Mask specific widget types
// Mask all IconButton widgets (masked by the widget type)
options.privacy.mask<IconButton>();
// Unmask Image widgets (show images in replay)
options.privacy.unmask<Image>();Custom masking logic per widget instance
options.privacy.maskCallback<Text>(
(Element element, Text widget) {
// Mask only text containing 'secret'
if (widget.data?.contains('secret') ?? false) {
return SentryMaskingDecision.mask;
}
return SentryMaskingDecision.continueProcessing;
},
);Disable all masking
Only do this if your app contains absolutely no sensitive data:
options.privacy.maskAllText = false;
options.privacy.maskAllImages = false;
options.privacy.maskAssetImages = false;Third-party widget masking
The SDK cannot automatically detect or mask third-party widgets. You must register them explicitly:
// Example: mask a map widget from a third-party package
options.privacy.mask<FlutterMap>();
// Example: mask a video player widget
options.privacy.mask<VideoPlayer>();---
5. What the Replay UI Shows
| Panel | Content |
|---|---|
| Video | Compressed screenshot sequence at ~1 fps |
| Breadcrumbs | User taps, navigation events, app lifecycle transitions |
| Timeline | Scrubbable view with event markers |
| Network | HTTP requests made during the session |
| Errors | All errors in the session linked to Sentry issues |
| Tags | OS version, device specs, release, user info, custom tags |
| Trace | All distributed traces during the replay session |
Touch / gesture recording
Touch interactions are recorded as breadcrumb events (discrete tap events), not raw gesture streams:
- ✅ Captured: Tap position, tapped widget, timestamp
- ❌ Not captured: Swipe paths, gesture velocity, multi-touch sequences
---
6. Session Lifecycle
| Event | Effect |
|---|---|
| SDK initializes / app enters foreground | New session starts |
| App goes to background | Session pauses |
| App returns to foreground within 30 seconds | Same session continues |
| App returns to foreground after 30+ seconds | New session starts |
| Session reaches 60 minutes | Session terminates |
| App crashes / closes in background | Session terminates abnormally |
The onErrorSampleRate mode keeps a pre-error buffer in memory. When an error occurs, this buffer plus the subsequent recording is captured and sent.
---
7. Performance Overhead
Session Replay adds CPU and memory overhead from periodic screenshot capture and compression.
| Metric | With Replay | Notes |
|---|---|---|
| CPU | +5–10% | During active recording |
| Memory | +15–25 MB | Screenshot buffer |
| FPS impact | -1 to -2 fps | Minimal on modern devices |
| Network bandwidth | ~7–10 KB/s | Compressed screenshot segments |
Reducing performance impact
options.replay.sessionSampleRate = 0.05; // lower session recording rate
options.replay.onErrorSampleRate = 1.0; // keep error capture at 100%Consider using onErrorSampleRate only (set sessionSampleRate to 0) for the lowest overhead while retaining the most valuable replay data.
---
8. Configuration Reference
options.replay settings
| Option | Type | Default | Description |
|---|---|---|---|
replay.sessionSampleRate | double (0–1) | 0 | Fraction of all sessions to record from start |
replay.onErrorSampleRate | double (0–1) | 0 | Fraction of error sessions to record (with pre-error buffer) |
options.privacy settings
| Option | Type | Default | Description |
|---|---|---|---|
privacy.maskAllText | bool | true | Mask Text, RichText, EditableText widgets |
privacy.maskAllImages | bool | true | Mask Image widgets |
privacy.maskAssetImages | bool | true | Mask asset bundle images |
privacy.mask<T>() | method | — | Mask all instances of widget type T |
privacy.unmask<T>() | method | — | Unmask all instances of widget type T |
privacy.maskCallback<T>() | method | — | Custom masking decision per widget instance |
Version requirements
| Feature | Min SDK |
|---|---|
| Session Replay (iOS + Android) | 9.0.0 |
options.replay.* configuration | 9.0.0 |
options.privacy.* masking | 9.0.0 |
---
9. Known Limitations
| Limitation | Details |
|---|---|
| iOS and Android only | Web, macOS, Linux, and Windows are not supported. Replay configuration is silently ignored on unsupported platforms. |
| ~1 fps frame rate | Not suitable for high-frequency UI debugging. Best for understanding flow and identifying the screen state during errors. |
| Text is not selectable | Screenshots capture pixels only — you cannot copy text from a replay. |
| No swipe/gesture paths | Only discrete tap events are recorded as breadcrumbs. Gesture trajectories are not captured. |
| Third-party widget masking | Must be registered manually via options.privacy.mask<WidgetType>() — the SDK cannot auto-detect foreign widget types. |
| Pre-error buffer is in-memory | Low-memory devices may have a shorter effective pre-error buffer. |
| No DOM inspection | Unlike web replay, you cannot inspect the widget tree state at a given frame — only the screenshot is available. |
---
10. Troubleshooting
| Issue | Solution |
|---|---|
| Replay not recording at all | Verify options.replay.sessionSampleRate or options.replay.onErrorSampleRate is > 0. Confirm SentryWidget wraps your root widget. |
| Platform not supported | Expected — Flutter Session Replay is iOS and Android only. Check you're building for a supported platform. |
| All content masked after disabling masking | Verify you've set options.privacy.maskAllText = false and options.privacy.maskAllImages = false before appRunner runs. |
| Third-party widget content visible despite masking | Register the widget type: options.privacy.mask<ThirdPartyWidget>() |
| High memory usage with replay enabled | Lower sessionSampleRate; consider using onErrorSampleRate only (set sessionSampleRate to 0) |
| Replay works in debug but not in production | Verify sample rates are non-zero in your production SentryFlutter.init() call; check DSN is correct for the environment |
| Pre-error buffer empty | Low memory device may have dropped the buffer. No workaround currently. |
| Replay sessions not linked to errors | Ensure both onErrorSampleRate > 0 and the SDK is initialized before the error occurs |
| Tap events missing in replay | Confirm SentryWidget is wrapping the widget tree — it intercepts gesture events to record taps |
Tracing & Performance — Flutter SDK Reference
Minimum SDK version: sentry_flutter ≥ 9.1.0 (routing TTID/TTFD), ≥ 6.17.0 (user interaction tracing)Platforms: Most features — all platforms. Platform-specific caveats noted per feature.
Setup
Enable tracing by setting tracesSampleRate in SentryFlutter.init():
await SentryFlutter.init(
(options) {
options.dsn = 'YOUR_SENTRY_DSN';
// Option A: Uniform sample rate (use 1.0 during development)
options.tracesSampleRate = 1.0;
// Option B: Dynamic sampler (takes precedence over tracesSampleRate)
options.tracesSampler = (samplingContext) {
final name = samplingContext.transactionContext?.name ?? '';
if (name.contains('checkout')) return 1.0; // always trace checkout
if (name.contains('health')) return 0.0; // never trace health checks
return 0.1; // 10% of everything else
};
},
appRunner: () => runApp(SentryWidget(child: MyApp())),
);Sampling precedence (highest to lowest): 1. Explicit sampled: true/false on the transaction 2. tracesSampler return value 3. Parent transaction sampling decision (distributed tracing) 4. tracesSampleRate static value
---
Navigation Instrumentation
Add SentryNavigatorObserver to automatically create a transaction per screen with Time to Initial Display (TTID) spans:
Standard MaterialApp / CupertinoApp
import 'package:sentry_flutter/sentry_flutter.dart';
MaterialApp(
navigatorObservers: [
SentryNavigatorObserver(
// Auto-finish idle transactions after this duration (default: 3s)
autoFinishAfter: const Duration(seconds: 3),
// Routes to exclude from tracking (default: [])
ignoreRoutes: ['/splash', '/loading'],
// Use route name as the Sentry transaction name (default: false)
setRouteNameAsTransaction: false,
// Generate a fresh trace on each push/pop/replace (default: false).
// Changed from true → false in sentry_flutter 9.19.0.
// Set to true to restore the previous opt-out behavior.
enableNewTraceOnNavigation: false,
),
],
routes: {
'/': (context) => HomeScreen(),
'/profile': (context) => ProfileScreen(),
},
)
// For anonymous routes — always provide a name:
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: 'ProductDetail'), // REQUIRED
builder: (context) => ProductDetailScreen(),
),
);⚠️ Routes must have names for TTID/TTFD spans and navigation breadcrumbs to be created. Unnamed routes show as null in Sentry.GoRouter
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
final GoRouter router = GoRouter(
observers: [SentryNavigatorObserver()],
routes: [
GoRoute(
path: '/',
name: 'home', // name is REQUIRED
builder: (context, state) => const HomeScreen(),
routes: [
GoRoute(
path: 'profile/:id',
name: 'profile', // name is REQUIRED
builder: (context, state) => ProfileScreen(
id: state.pathParameters['id']!,
),
),
],
),
],
);Known limitation: GoRouter does not propagate navigator observers across same-level tab transitions. Bottom-tab navigation events may not be tracked.
Auto Route (Community Pattern)
import 'package:auto_route/auto_route.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
MaterialApp.router(
routerConfig: _router.config(
navigatorObservers: () => [SentryNavigatorObserver()],
),
)Known limitation: Tab-based nested navigation is not captured (tracked issue getsentry/sentry-dart#2123).
---
Time to Full Display (TTFD)
TTID (Time to Initial Display) is always enabled — measures to the first rendered frame. TTFD measures to when your screen is fully ready (data loaded, async work done). TTFD is opt-in:
// Enable in options
options.enableTimeToFullDisplayTracing = true;Then report when the screen is fully ready:
// Option 1: Widget wrapper (marks TTFD when child's build() completes)
// For StatelessWidget — TTFD is auto-reported when build() returns.
// For StatefulWidget — call reportFullyDisplayed() manually in a wrapper:
Navigator.push(
context,
MaterialPageRoute(
settings: const RouteSettings(name: 'ProductScreen'),
builder: (context) => SentryDisplayWidget(child: ProductScreen()),
),
);
// Inside ProductScreen (StatefulWidget):
@override
void initState() {
super.initState();
_loadData().then((_) {
if (mounted) {
SentryDisplayWidget.of(context).reportFullyDisplayed();
}
});
}
// Option 2: Direct API call (from anywhere in the widget)
await _loadData();
SentryFlutter.currentDisplay()?.reportFullyDisplayed();TTFD auto-expires at 30 seconds with SpanStatus.DEADLINE_EXCEEDED if reportFullyDisplayed() is never called.
---
User Interaction Tracing
Automatically creates transactions for taps, clicks, and long presses. Requires SentryWidget at the root:
appRunner: () => runApp(SentryWidget(child: MyApp()))Widgets must have a `Key` to be tracked:
ElevatedButton(
key: const Key('submit_payment_button'), // REQUIRED for tracking
onPressed: _submitPayment,
child: const Text('Pay Now'),
)Transactions are named ButtonWidget key[submit_payment_button] with operation ui.action.click. Empty transactions (no child spans within idleTimeout) are silently dropped.
Configuration:
options.enableUserInteractionTracing = true; // default: true
options.enableUserInteractionBreadcrumbs = true; // default: true
options.sendDefaultPii = false; // set true to include widget text as labels
options.idleTimeout = const Duration(milliseconds: 3000); // default---
HTTP Tracing
Standard http package (built into sentry_flutter)
import 'package:sentry/sentry.dart';
// Wrap any active transaction with an HTTP client for automatic spans
final transaction = Sentry.startTransaction('api-call', 'request', bindToScope: true);
final client = SentryHttpClient(
captureFailedRequests: true,
failedRequestStatusCodes: [SentryStatusCode.range(400, 599)],
failedRequestTargets: ['api.example.com'],
);
try {
final response = await client.get(Uri.parse('https://api.example.com/users'));
} finally {
client.close();
await transaction.finish(status: SpanStatus.ok());
}Dio (sentry_dio)
dio.addSentry() must be the last step in Dio setup:
import 'package:sentry_dio/sentry_dio.dart';
final dio = Dio();
dio.interceptors.add(MyAuthInterceptor()); // your interceptors first
dio.addSentry( // addSentry() always last
captureFailedRequests: true,
failedRequestStatusCodes: [SentryStatusCode.range(400, 599)],
);
// Spans only appear if a transaction is on scope
final transaction = Sentry.startTransaction('api-work', 'request', bindToScope: true);
await dio.get('https://api.example.com/products');
await transaction.finish(status: SpanStatus.ok());Distributed Tracing
Propagate trace context to your backend (links mobile spans to server spans in the waterfall view):
options.tracePropagationTargets = ['api.myapp.com', 'localhost'];
options.propagateTraceparent = true; // also send W3C traceparent header (SDK ≥9.7.0)---
App Start Instrumentation
Platforms: iOS and Android only. Automatically enabled — no config needed.
Measures from earliest native process init to first Flutter frame. Creates:
ui.loadtransactionapp.start.coldorapp.start.warmspan
⚠️ Does not work accurately in Flutter add-to-app scenarios.
Disable if needed:
import 'package:sentry_flutter/src/integrations/native_app_start_integration.dart';
options.integrations.removeWhere((i) => i is NativeAppStartIntegration);---
Slow and Frozen Frames
Platforms: iOS, Android, macOS. Auto-enabled when Sentry initializes before WidgetsFlutterBinding.
Thresholds:
- Slow frame: > 16ms render time (misses 60 FPS target)
- Frozen frame: > 700ms render time
If you need to call WidgetsFlutterBinding.ensureInitialized() before Sentry (e.g., for plugin setup), use:
// Replace WidgetsFlutterBinding.ensureInitialized() with:
SentryWidgetsFlutterBinding.ensureInitialized();Disable:
options.enableFramesTracking = false;---
Custom Instrumentation
Manual Transaction
final transaction = Sentry.startTransaction(
'processOrderBatch()', // name
'task', // operation
bindToScope: true, // links auto-captured errors to this transaction
);
try {
await processOrderBatch();
transaction.status = const SpanStatus.ok();
} catch (exception, stackTrace) {
transaction.throwable = exception;
transaction.status = const SpanStatus.internalError();
await Sentry.captureException(exception, stackTrace: stackTrace);
} finally {
await transaction.finish();
}Nested Spans
final span = Sentry.getSpan()?.startChild(
'db.query',
description: 'SELECT * FROM orders WHERE user_id = ?',
) ?? Sentry.startTransaction('fallback', 'task', bindToScope: true);
try {
final orders = await db.getOrdersForUser(userId);
span.status = const SpanStatus.ok();
return orders;
} catch (e, st) {
span.throwable = e;
span.status = const SpanStatus.notFound();
rethrow;
} finally {
await span.finish();
}Attach Data to Spans
transaction.setData('user_id', userId);
transaction.setData('items_count', 42);
transaction.setData('currency', 'USD');
// Supported types: String, int, double, bool, ListDynamic Sampling with Context
final transaction = Sentry.startTransaction(
'processPayment()',
'task',
customSamplingContext: {
'payment.amount': amount,
'payment.currency': currency,
'user.tier': userTier,
},
);
// In tracesSampler:
options.tracesSampler = (ctx) {
final tier = ctx.customSamplingContext?['user.tier'];
if (tier == 'enterprise') return 1.0; // always trace enterprise
return 0.1;
};---
Database Tracing
All DB integrations require an active transaction on scope to generate spans.
sqflite (sentry_sqflite) — Android, iOS, macOS only
import 'package:sentry_sqflite/sentry_sqflite.dart';
// Global: instruments ALL databases
databaseFactory = SentrySqfliteDatabaseFactory();
// Per-database (recommended)
final db = await openDatabaseWithSentry('my.db');Drift (sentry_drift) — All platforms
import 'package:sentry_drift/sentry_drift.dart';
final executor = driftDatabase(name: 'app').interceptWith(
SentryQueryInterceptor(databaseName: 'app'),
);
final db = AppDatabase(executor);Hive (sentry_hive) — All platforms
import 'package:sentry_hive/sentry_hive.dart';
// Use SentryHive instead of Hive
SentryHive.init(appDir.path);
final box = await SentryHive.openBox<Person>('people');Isar (sentry_isar) — Android, iOS, macOS, Linux, Windows (no Web)
import 'package:sentry_isar/sentry_isar.dart';
// Use SentryIsar.open() instead of Isar.open()
final isar = await SentryIsar.open([UserSchema], directory: dir.path);---
Asset Bundle Instrumentation
Traces asset loading in your app (built into sentry_flutter):
runApp(
DefaultAssetBundle(
bundle: SentryAssetBundle(enableStructuredDataTracing: true),
child: MyApp(),
),
)---
Production Configuration
final isProduction = const bool.fromEnvironment('dart.vm.product');
await SentryFlutter.init(
(options) {
options.dsn = const String.fromEnvironment('SENTRY_DSN');
// 10% sampling in production, 100% in dev
options.tracesSampleRate = isProduction ? 0.1 : 1.0;
// Trace all checkout flows, drop health checks
options.tracesSampler = (ctx) {
final name = ctx.transactionContext?.name ?? '';
if (name.contains('checkout') || name.contains('payment')) return 1.0;
if (name.contains('health') || name.contains('ping')) return 0.0;
return isProduction ? 0.1 : 1.0;
};
// Connect traces to backend
options.tracePropagationTargets = ['api.myapp.com'];
},
appRunner: () => runApp(SentryWidget(child: MyApp())),
);---
Troubleshooting
| Issue | Solution |
|---|---|
| No transactions in Sentry | Confirm tracesSampleRate > 0; set to 1.0 to see all during debugging |
| Navigation not tracked | Add SentryNavigatorObserver() to navigatorObservers; name all routes |
| Traces not being connected across navigations | enableNewTraceOnNavigation defaults to false since 9.19.0 — set it to true to generate a fresh trace on each navigation event |
| TTID/TTFD spans missing | SDK ≥ 9.1.0 required; routes must have names |
| TTFD never reports | Call SentryFlutter.currentDisplay()?.reportFullyDisplayed() or use SentryDisplayWidget |
| GoRouter tabs not tracked | Known Flutter limitation — tab transitions don't trigger standard navigator callbacks |
| User interaction transactions empty | Widget must have a Key; spans need child work before idleTimeout |
| Dio spans not appearing | Confirm bindToScope: true on the parent transaction; dio.addSentry() must be last |
| HTTP spans not appearing | Wrap requests with a scope-bound transaction; SentryHttpClient only creates spans in a transaction context |
| App start metrics missing | iOS/Android only; not available in add-to-app; disable with NativeAppStartIntegration removal |
| Slow/frozen frames not tracked | Sentry must init before WidgetsFlutterBinding; use SentryWidgetsFlutterBinding.ensureInitialized() |
tracesSampler not being called | Only called once per transaction creation — check that transactions are being started |
| gRPC requests slow on Windows | Known issue (getsentry/sentry-dart#2760) — startTransaction overhead; avoid or use async span creation |
| Too many transactions in dashboard | Lower tracesSampleRate or use tracesSampler to drop noisy endpoints |
Related skills
How it compares
Use for guided Flutter Sentry install; pick other sentry-sdk-setup children for React, Node, or native iOS/Android without Flutter.
FAQ
What does Phase 1 Detect inspect?
pubspec dependencies, existing SentryFlutter.init calls, navigation, HTTP, databases, and platform folders.
Which platforms are supported?
Android, iOS, macOS, Linux, Windows, and Web with native crash and ANR monitoring.
What SDK version does the skill assume?
sentry_flutter 9.14.0 or newer; verify against current Sentry Flutter docs before implementing.
Is Sentry Flutter Sdk safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.