---
title: "Routing Instrumentation"
description: "Learn more about the Sentry Routing Instrumentation for the Flutter SDK."
url: https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation/
---

# Routing Instrumentation | Sentry for Flutter

References to "transactions" on this page apply to the default transaction mode. In stream mode, this integration creates service spans instead, and Sentry sends them as they finish. See [Streamed Spans](https://docs.sentry.io/platforms/dart/guides/flutter/tracing/streamed-spans.md) for more information.

Sentry's routing instrumentation for Flutter automatically tracks and reports page navigation events in your app. It supports imperative navigation (`Navigator.push()`), the declarative `Router` API (`MaterialApp.router`), and popular routing packages like [GoRouter](https://pub.dev/packages/go_router) and [auto\_route](https://pub.dev/packages/auto_route).

The routing instrumentation feature is shipped with Sentry's Flutter SDK automatically.

## [Prerequisites](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#prerequisites)

Before starting, ensure:

1. The Sentry Flutter SDK `9.1.0` or later is installed.
2. The Sentry Flutter SDK is initialized. Learn more [here](https://docs.sentry.io/platforms/dart/guides/flutter.md#configure).
3. Tracing is set up. Learn more [here](https://docs.sentry.io/platforms/dart/guides/flutter/tracing.md).

## [Configure](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#configure)

### [1. Add `SentryNavigatorObserver`](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-add-sentrynavigatorobserver)

How you add `SentryNavigatorObserver` depends on which navigation approach you use.

**Navigator**

```dart
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  await SentryFlutter.init((options) {
    options.dsn = 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>';
  }, appRunner: () => runApp(SentryWidget(child: MyApp())));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        navigatorObservers: [
          SentryNavigatorObserver(),
        ],
        ...
    );
  }
}
```

**Router**

```dart
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

// When using a custom RouterDelegate, pass the observer
// to the Navigator you build inside the delegate's build() method.
class MyRouterDelegate extends RouterDelegate<MyRoutePath>
    with ChangeNotifier, PopNavigatorRouterDelegateMixin<MyRoutePath> {
  @override
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    return Navigator(
      key: navigatorKey,
      observers: [SentryNavigatorObserver()],
      pages: [
        // Your pages based on app state
      ],
      onDidRemovePage: (page) {
        // Update your app state based on the removed page
        notifyListeners();
      },
    );
  }

  // ... implement setNewRoutePath and other required methods
}

Future<void> main() async {
  await SentryFlutter.init((options) {
    options.dsn = 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>';
  }, appRunner: () => runApp(SentryWidget(child: MyApp())));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerDelegate: MyRouterDelegate(),
      routeInformationParser: MyRouteInformationParser(),
    );
  }
}
```

**GoRouter**

```dart
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:go_router/go_router.dart';

final _router = GoRouter(
  routes: [
    ...
  ],
  observers: [SentryNavigatorObserver()],
);

Future<void> main() async {
  await SentryFlutter.init((options) {
    options.dsn = 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>';
  }, appRunner: () => runApp(SentryWidget(child: MyApp())));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _router,
    );
  }
}
```

**AutoRoute**

```dart
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:auto_route/auto_route.dart';

// Your generated router
final _appRouter = AppRouter();

Future<void> main() async {
  await SentryFlutter.init((options) {
    options.dsn = 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>';
  }, appRunner: () => runApp(SentryWidget(child: MyApp())));
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: _appRouter.config(
        navigatorObservers: () => [
          SentryNavigatorObserver(),
        ],
      ),
    );
  }
}
```

### [2. Define Route Names](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-define-route-names)

The instrumentation sets the span `operation` to `ui.load` and the span `name` to the provided route name.

How you define route names depends on your navigation approach:

* **Navigator (imperative)**: Pass a `name` in the `RouteSettings` when pushing routes.
* **Router**: Set the `name` on the `Page` objects you return in your `pages` list (for example, `MaterialPage(name: 'My Widget', child: ...)`).
* **GoRouter**: GoRouter automatically uses the route `path` as the name. You can optionally set the `name` parameter on your `GoRoute` to override it.
* **auto\_route**: Route names are generated automatically from your `@RoutePage()` annotations — no extra configuration needed.

If the SDK cannot determine a route name, it will not create transactions, breadcrumbs, or performance insights such as [TTID](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-initial-display) or [TTFD](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-full-display) for that route.

**Navigator**

```dart
MaterialPageRoute(
  builder: (BuildContext context) => MyWidget(),
  settings: RouteSettings(name: 'My Widget'),
)
```

**Router**

```dart
// Set the name on the Page objects in your RouterDelegate's pages list.
MaterialPage(
  name: 'My Widget',
  child: MyWidget(),
)
```

**GoRouter**

```dart
// The path is used as the route name by default.
// You can optionally set name to override it.
GoRoute(
  path: '/mywidget',
  name: 'My Widget', // optional, falls back to path
  builder: (BuildContext context, GoRouterState state) {
    return const MyWidget();
  }
)
```

**AutoRoute**

```dart
// Route names are auto-generated from your page annotations.
// No additional configuration is needed.
@RoutePage()
class MyWidgetPage extends StatelessWidget {
  // ...
}
```

## [Time to Initial Display](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-initial-display)

Time to initial display (TTID) provides insight into how long it takes your Widget to launch and draw their first frame. This is measured by adding a span for navigation to a Widget. The SDK then sets the span operation to `ui.load.initial-display` and the span description to the Widget's route name, followed by initial display (for example, `MyWidget initial display`).

TTID is enabled by default.

## [Time to Full Display](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#time-to-full-display)

Time to full display (TTFD) provides insight into how long it would take your Widget to launch and load all of its content. This is measured by adding a span for each navigation to a Widget. The SDK then sets the span operation to `ui.load.full-display` and the span description to the Widget's route name, followed by full display (for example, `MyWidget full display`).

TTFD is disabled by default. To enable TTFD measurements, follow these steps:

### [1. Enable the `enableTimeToFullDisplayTracing` option in the SDK configuration](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-enable-the-enabletimetofulldisplaytracing-option-in-the-sdk-configuration)

```dart
await SentryFlutter.init(
  (options) {
    options.dsn = 'https://<key>@o<orgId>.ingest.sentry.io/<projectId>';
    options.enableTimeToFullDisplayTracing = true;
  }, appRunner: () => runApp(SentryWidget(child: MyApp())),
);
```

### [2. Report the TTFD span](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-report-the-ttfd-span)

There are two ways to report when your widget is fully displayed:

#### [1. Wrap with `SentryDisplayWidget`](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-wrap-with-sentrydisplaywidget)

Embed your target widget in `SentryDisplayWidget` and call `SentryDisplayWidget.of(context).reportFullyDisplayed()`.

#### [2. Call the API directly](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-call-the-api-directly)

Retrieve the current display span via `SentryFlutter.currentDisplay()` in `initState()` and call `currentDisplay.reportFullyDisplayed()` — no wrapper needed.

**Important for `StatelessWidget`:**

If you're navigating to a `StatelessWidget`, you must use the `SentryDisplayWidget` wrapper. `SentryDisplayWidget` automatically reports TTFD as soon as the build completes. You do not need to call `reportFullyDisplayed()` yourself.

**Wrap with SentryDisplayWidget**

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    settings: const RouteSettings(name: 'MyWidget'),
    builder: (context) => SentryDisplayWidget(child: MyWidget()),
  ),
);

// Inside MyWidget’s State:
@override
void initState() {
  super.initState();
  // Do some long running work...
  Future.delayed(const Duration(seconds: 3), () {
    if (mounted) {
      SentryDisplayWidget.of(context).reportFullyDisplayed();
    }
  });
}
```

**Use the API Directly**

```dart
Navigator.push(
  context,
  MaterialPageRoute(
    settings: const RouteSettings(name: 'MyWidget'),
    builder: (context) => MyWidget(),
  ),
);

// Inside MyWidget’s State:
@override
void initState() {
  super.initState();
  // Get a reference to the current display before doing work.
  final currentDisplay = SentryFlutter.currentDisplay();
  // Do some long running work...
  Future.delayed(const Duration(seconds: 3), () {
    currentDisplay?.reportFullyDisplayed();
  });
}
```

If the span finishes through the API, its status will be set to `SpanStatus.OK`.

If the span doesn't finish after 30 seconds, it will be finished by the SDK automatically, and its status will be set to `SpanStatus.DEADLINE_EXCEEDED`. In this case, its duration will match the TTID span.

## [Verify](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#verify)

### [1. Implement a Test Widget:](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#1-implement-a-test-widget)

Set up a new widget that executes an expensive operation.

```dart
import 'package:flutter/material.dart';
import 'package:sentry/sentry.dart';

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

  @override
  MyWidgetState createState() => MyWidgetState();
}

class MyWidgetState extends State<MyWidget> {
  @override
  void initState() {
    super.initState();
    Future.delayed(const Duration(seconds: 3), () {
      if (mounted) {
        SentryDisplayWidget.of(context).reportFullyDisplayed();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return ...
  }
}
```

### [2. Navigate to the Widget:](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#2-navigate-to-the-widget)

Use the navigator to transition to your widget. This should create and send a transaction named after the widget's route.

**Navigator**

```dart
import 'package:flutter/material.dart';
import 'my_widget.dart';

/// Push to a new screen
Navigator.push(
  context,
  MaterialPageRoute(
    settings: const RouteSettings(name: 'MyWidget'),
    builder: (context) => SentryDisplayWidget(child: MyWidget()),
  ),
);
```

**Router**

```dart
// Update your delegate's state to trigger a page change.
final delegate = Router.of(context).routerDelegate as MyRouterDelegate;
delegate.showDetail();
```

**GoRouter**

```dart
import 'package:go_router/go_router.dart';
import 'package:sentry_flutter/sentry_flutter.dart';

context.push('/mywidget')
```

**AutoRoute**

```dart
import 'package:auto_route/auto_route.dart';

context.pushRoute(const MyWidgetRoute());
```

Log into [sentry.io](https://sentry.io) and open your project's performance page to see the transaction `MyWidget`.

## [Additional Configuration](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#additional-configuration)

### [Auto Finish Time](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#auto-finish-time)

Adjust the duration before a routing transaction automatically finishes. The default is 3 seconds.

```dart
SentryNavigatorObserver(
  autoFinishAfter: Duration(seconds: 5)
)
```

When configuring the `autoFinishAfter` parameter, consider the following behaviours:

* Started child spans will be attached to the navigation transaction - for example the `MyWidget` transaction.
* If child spans finish after the `autoFinishAfter` time, the transaction extends and finishes when all child spans finished.
* If child spans finish before the `autoFinishAfter` time, the transaction's end time will be set to the last child end time.

### [Breadcrumb Tracking Only](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#breadcrumb-tracking-only)

Set `enableAutoTransactions` to `false` if you only want to track navigation breadcrumbs. Enabled by default.

```dart
SentryNavigatorObserver(
  enableAutoTransactions: false,
)
```

### [Ignore Routes](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#ignore-routes)

Set `ignoreRoutes` if you want routes to be ignored and not processed by the navigation observer. Empty by default.

```dart
SentryNavigatorObserver(
  ignoreRoutes: ["/ignoreThisRoute", "/my/ignored/route"],
)
```

### [Override Transaction Name](https://docs.sentry.io/platforms/dart/guides/flutter/integrations/routing-instrumentation.md#override-transaction-name)

Set `setRouteNameAsTransaction` to `true` to override the transaction name with the route name. An existing transaction in the scope 'CustomTransaction' will be renamed to 'MyWidget' for example. Disabled by default.

```dart
SentryNavigatorObserver(
  setRouteNameAsTransaction: true,
)
```
