Passing Parameters and Query Strings with GoRouter
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');
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');
/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',
},
);
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 locationcontext.push('/product/42', extra: product)— push onto the stackcontext.goNamed('product', pathParameters: {'id': '42'})— navigate by route namecontext.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.
extra only as a performance shortcut, never as the sole source of truth for a screen.