Navigation & Routing

Passing Parameters and Query Strings with GoRouter

16 min Lesson 10 of 14

Passing Parameters and Query Strings with GoRouter

One of the most important capabilities in any router is the ability to pass data between screens. GoRouter provides three complementary mechanisms for this: path parameters (e.g., /product/:id), query parameters (e.g., ?sort=price&page=2), and the extra argument for passing arbitrary Dart objects that cannot be encoded in a URL string. Mastering all three lets you build clean, deep-linkable navigation.

Path Parameters

Path parameters are variable segments embedded directly in the URL path, prefixed with a colon. They are ideal for required, identity-style data — like a product ID, a username, or a post slug — because the resource is meaningless without them.

You declare them in the route template and read them from GoRouterState.pathParameters inside the builder callback:

Defining and Reading Path Parameters

// Route definition — :id is the path parameter
GoRoute(
  path: '/product/:id',
  builder: (BuildContext context, GoRouterState state) {
    // pathParameters is a Map<String, String>
    final String productId = state.pathParameters['id']!;
    return ProductDetailPage(productId: productId);
  },
),

// Navigate with a concrete value
context.go('/product/42');
context.go('/product/abc-xyz-789');
Note: Path parameters are always String. If your model uses an int ID, parse it explicitly: int.parse(state.pathParameters['id']!). Use int.tryParse defensively and redirect to a 404 screen when the value is missing or non-numeric.

Multiple Path Parameters

A single route can contain more than one path segment parameter. Each colon-prefixed segment becomes a separate key in pathParameters:

Route with Multiple Parameters

GoRoute(
  path: '/shop/:category/:productId',
  builder: (BuildContext context, GoRouterState state) {
    final String category  = state.pathParameters['category']!;
    final String productId = state.pathParameters['productId']!;

    return ProductPage(
      category: category,
      productId: int.parse(productId),
    );
  },
),

// Navigate
context.go('/shop/electronics/101');
context.go('/shop/books/978');

Query Parameters

Query parameters appear after the ? in a URL and are entirely optional. They are perfect for filtering, sorting, pagination, and feature flags — context that enriches a screen without being part of the resource identity.

Read them from GoRouterState.uri.queryParameters, which returns a Map<String, String>:

Defining a Route That Reads Query Parameters

GoRoute(
  path: '/products',
  builder: (BuildContext context, GoRouterState state) {
    // queryParameters is Map<String, String> — all values optional
    final String? sort   = state.uri.queryParameters['sort'];
    final String? search = state.uri.queryParameters['search'];
    final int     page   = int.tryParse(
                             state.uri.queryParameters['page'] ?? '',
                           ) ?? 1;

    return ProductListPage(
      sort: sort,
      search: search,
      page: page,
    );
  },
),

// Navigate with query parameters
context.go('/products?sort=price&page=2');
context.go('/products?search=flutter&sort=rating');
Tip: Combine path and query parameters freely. A route like /category/:slug can still receive query params: context.go('/category/books?sort=newest&page=3'). Both pathParameters and uri.queryParameters are populated simultaneously.

Passing Complex Objects with extra

URL strings can only carry text. When you need to forward a full Dart object — a pre-fetched model, a list of items, or a configuration struct — use the extra argument. It accepts any Object? and travels through the navigation stack without URL serialization.

Using the extra Argument

// 1. Define the route to cast extra back to its type
GoRoute(
  path: '/product/:id',
  builder: (BuildContext context, GoRouterState state) {
    // extra is typed as Object? — cast carefully
    final Product? product = state.extra as Product?;
    final String  productId = state.pathParameters['id']!;

    // If extra is null (e.g. direct deep-link), fetch by ID instead
    if (product != null) {
      return ProductDetailPage(product: product);
    }
    return ProductDetailPage.fromId(productId: productId);
  },
),

// 2. Pass the object when navigating
final Product selectedProduct = Product(
  id: '42',
  name: 'Wireless Headphones',
  price: 129.99,
);

context.go('/product/42', extra: selectedProduct);

// 3. Pass a Map when you have multiple loose values
context.go(
  '/checkout',
  extra: <String, dynamic>{
    'cartItems': cart.items,
    'promoCode': 'FLUTTER20',
  },
);
Warning: Objects passed via extra are not preserved when the user deep-links directly to a URL (e.g., typing the URL in a browser or tapping a push notification). Always implement a fallback that fetches data by ID when extra is null. This is especially critical for web builds and apps that support universal links.

Navigating with GoRouter.push vs go

Both context.go() and context.push() accept the full parameter set. Use go to replace the current location (no back button to the previous screen), and push to add a new screen on top of the stack (preserves back navigation).

  • context.go('/product/42', extra: product) — replace current location
  • context.push('/product/42', extra: product) — push onto the stack
  • context.goNamed('product', pathParameters: {'id': '42'}) — navigate by route name
  • context.pushNamed('product', pathParameters: {'id': '42'}, queryParameters: {'tab': 'reviews'}) — push with named route + query params

Summary

GoRouter gives you three parameter channels that complement each other:

  • Path parameters (state.pathParameters) — required, identity-forming URL segments like /user/:id.
  • Query parameters (state.uri.queryParameters) — optional key-value pairs after ?, great for filters and pagination.
  • extra — in-memory Dart objects, ideal when you already have the data and want to avoid a redundant network call, but always provide an ID-based fallback for deep-link safety.
Key Takeaway: Design your routes so that path and query parameters alone can reconstruct any screen from a cold start. Use extra only as a performance shortcut, never as the sole source of truth for a screen.