# Flutter Deep Linking with go_router > Master Flutter deep linking with go_router. Learn route configuration, path parameters, redirect guards, and deferred deep link handling. - Canonical: https://redirectly.app/blog/flutter-deep-linking-go-router - Site: [Redirectly](https://redirectly.app) โ€” deferred deep linking for Flutter & React Native - Published: 2024-02-20 - Author: Redirectly - Category: Flutter ยท 8 min read Learn how to implement powerful deep linking in Flutter using go_router. Master route configuration, path parameters, redirect guards, and deferred deep link handling with complete code examples. ## Table of Contents 1. Why go_router for Deep Linking? 2. Getting Started with go_router 3. Configuring Deep Link Routes 4. Path Parameter Extraction 5. Redirect Guards & Auth Checks 6. Deferred Deep Link Handling 7. Full Working Example ## Why go_router for Deep Linking? ### Key Advantages - Declarative route configuration - Built-in deep link support - Path parameter extraction - Redirect guards for auth - Deep link state tracking - Async parameter parsing ### Deep Linking Features - Automatic link parsing - Named route parameters - Query parameter support - Fallback routes - Error handling - State preservation ### go_router vs Manual Handling While uni_links captures deep links, go_router handles the routing logic. Together, they create a complete deep linking solution with declarative route configuration and automatic URL parsing. ## Getting Started with go_router ### Step 1: Add Dependencies Add go_router and uni_links to your pubspec.yaml: ```yaml dependencies: flutter: sdk: flutter go_router: ^13.0.0 uni_links: ^0.9.0 ``` ### Step 2: Install Packages Run flutter pub get: ```bash flutter pub get ``` ## Configuring Deep Link Routes ### Create Router Configuration Set up your GoRouter with deep link routes: ```dart import 'package:go_router/go_router.dart'; import 'package:flutter/material.dart'; final GoRouter router = GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ // Product route with parameter GoRoute( path: 'product/:productId', builder: (context, state) { final productId = state.pathParameters['productId']!; return ProductScreen(productId: productId); }, ), // User profile route GoRoute( path: 'user/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return UserProfileScreen(userId: userId); }, ), // Referral/share route GoRoute( path: 'share/:referralCode', builder: (context, state) { final referralCode = state.pathParameters['referralCode']!; return ShareScreen(referralCode: referralCode); }, ), ], ), // 404 not found route GoRoute( path: '/404', builder: (context, state) => const NotFoundScreen(), ), ], // Handle unmatched routes errorBuilder: (context, state) => const NotFoundScreen(), ); ``` ### Use Router in Main App Initialize the router in your MaterialApp: ```dart void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp.router( title: 'My App', routerConfig: router, theme: ThemeData( primarySwatch: Colors.blue, ), ); } } ``` ## Path Parameter Extraction ### Accessing Path Parameters Extract and use path parameters in your screens: ```dart class ProductScreen extends StatelessWidget { final String productId; const ProductScreen({Key? key, required this.productId}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Product')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Product ID: $productId'), const SizedBox(height: 20), ElevatedButton( onPressed: () { // Load product details _loadProductDetails(productId); }, child: const Text('Load Product'), ), ], ), ), ); } Future _loadProductDetails(String productId) async { debugPrint('Loading product: $productId'); // Fetch from API using productId } } ``` ### Query Parameters Access query parameters from the deep link: ```dart GoRoute( path: 'product/:productId', builder: (context, state) { final productId = state.pathParameters['productId']!; // Access query parameters final String? source = state.uri.queryParameters['source']; final String? campaign = state.uri.queryParameters['campaign']; return ProductScreen( productId: productId, source: source, campaign: campaign, ); }, ), ``` ## Redirect Guards & Auth Checks ### Implement Auth Guard Protect routes with authentication redirects: ```dart final GoRouter router = GoRouter( redirect: (context, state) { // Check if user is authenticated final isAuthenticated = _isUserAuthenticated(); final isLoggingIn = state.uri.path == '/login'; if (!isAuthenticated && !isLoggingIn) { // Redirect to login, but save the requested path return '/login?from=${state.uri.path}'; } if (isAuthenticated && isLoggingIn) { // Already logged in, redirect to home return '/'; } return null; // No redirect needed }, routes: [ // Your routes here ], ); ``` ### Route-Level Guards Add guards to specific routes using redirect: ```dart GoRoute( path: 'user/:userId', redirect: (context, state) { // Check if user has permission to view final userId = state.pathParameters['userId']!; final currentUserId = _getCurrentUserId(); if (userId != currentUserId && !_isAdmin()) { return '/'; // Redirect to home if no permission } return null; // Allow access }, builder: (context, state) { final userId = state.pathParameters['userId']!; return UserProfileScreen(userId: userId); }, ), ``` ## Deferred Deep Link Handling ### Handle Deferred Links with go_router Implement deferred deep link handling with initial link detection: ```dart import 'package:uni_links/uni_links.dart'; class DeepLinkManager { static Future initializeDeepLinks(GoRouter router) async { // Handle initial deep link (app launched from deep link) final String? initialLink = await getInitialLink(); if (initialLink != null) { await _processDeepLink(initialLink, router); } // Handle deep links while app is running linkStream.listen( (String link) { _processDeepLink(link, router); }, onError: (err) { debugPrint('Deep link error: $err'); }, ); } static Future _processDeepLink( String link, GoRouter router, ) async { final Uri uri = Uri.parse(link); try { // For Universal Links/App Links, use go_router's built-in handling if (uri.scheme == 'https') { // Construct the path from the URI final String path = uri.path; final String? queryString = uri.query.isNotEmpty ? '?${uri.query}' : ''; // Use go_router to navigate router.go('$path$queryString'); } else if (uri.scheme == 'myapp') { // Custom scheme handling router.go(uri.path, extra: uri.queryParameters); } } catch (e) { debugPrint('Error processing deep link: $e'); } } } ``` ### Initialize in Main App Initialize deep link handling in your main app: ```dart class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { late final GoRouter _router; @override void initState() { super.initState(); _router = _createRouter(); // Initialize deep link handling after router is created WidgetsBinding.instance.addPostFrameCallback((_) { DeepLinkManager.initializeDeepLinks(_router); }); } GoRouter _createRouter() { return GoRouter( routes: [ // Your routes ], ); } @override Widget build(BuildContext context) { return MaterialApp.router( title: 'My App', routerConfig: _router, ); } } ``` ## Full Working Example ### Complete App Implementation Here's a complete example app with deep linking: ```dart import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:uni_links/uni_links.dart'; import 'dart:async'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({Key? key}) : super(key: key); @override State createState() => _MyAppState(); } class _MyAppState extends State { late final GoRouter _router = _createRouter(); @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { _initializeDeepLinks(); }); } GoRouter _createRouter() { return GoRouter( routes: [ GoRoute( path: '/', builder: (context, state) => const HomeScreen(), routes: [ GoRoute( path: 'product/:productId', builder: (context, state) { final productId = state.pathParameters['productId']!; return ProductScreen(productId: productId); }, ), GoRoute( path: 'user/:userId', builder: (context, state) { final userId = state.pathParameters['userId']!; return UserProfileScreen(userId: userId); }, ), ], ), ], errorBuilder: (context, state) => const NotFoundScreen(), ); } Future _initializeDeepLinks() async { final String? initialLink = await getInitialLink(); if (initialLink != null) { _router.go(_parseDeepLink(initialLink)); } linkStream.listen((String link) { _router.go(_parseDeepLink(link)); }); } String _parseDeepLink(String link) { final uri = Uri.parse(link); return uri.path; } @override Widget build(BuildContext context) { return MaterialApp.router( title: 'Deep Link App', routerConfig: _router, ); } } // Screens class HomeScreen extends StatelessWidget { const HomeScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Home')), body: const Center(child: Text('Home Screen')), ); } } class ProductScreen extends StatelessWidget { final String productId; const ProductScreen({Key? key, required this.productId}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Product')), body: Center( child: Text('Product ID: $productId'), ), ); } } class UserProfileScreen extends StatelessWidget { final String userId; const UserProfileScreen({Key? key, required this.userId}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('User Profile')), body: Center( child: Text('User ID: $userId'), ), ); } } class NotFoundScreen extends StatelessWidget { const NotFoundScreen({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Not Found')), body: const Center( child: Text('Page not found'), ), ); } } ``` ## Best Practices ### Route Organization - Keep route definitions modular - Extract routes into separate files - Use descriptive path names - Group related routes together - Document route parameters - Version routes if needed ### Error Handling - Always provide error routes - Log invalid deep links - Provide fallback navigation - Validate parameters - Handle null safely - Test edge cases **Pro Tips** - Use RouteMatch to debug route parsing - Test deep links with adb on Android and xcode-select on iOS - Preserve state during navigation with keepAlive - Use query parameters for optional data - Combine with Riverpod or GetX for state management ## Master Complete Deep Linking You now have the knowledge to implement complete deep linking with go_router. Learn about deferred deep linking for advanced attribution and app install tracking. - [Deferred Deep Linking](https://redirectly.app/flutter-deferred-deep-linking.md) - [Learn More](https://redirectly.app/deferred-deep-linking.md) ## Related Articles - [iOS Universal Links Setup](https://redirectly.app/blog/flutter-universal-links-ios-setup.md) โ€” Complete guide to configuring Universal Links in Flutter with Xcode and AASA files. - [Android App Links Setup](https://redirectly.app/blog/flutter-android-app-links-setup.md) โ€” Step-by-step guide to setting up App Links with assetlinks.json and SHA-256. --- Full site index for AI agents: https://redirectly.app/llms.txt