{"id":887,"date":"2026-04-23T13:00:00","date_gmt":"2026-04-23T18:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=887"},"modified":"2026-03-07T03:48:32","modified_gmt":"2026-03-07T08:48:32","slug":"flutter-deep-linking-setup","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/flutter-deep-linking-setup\/","title":{"rendered":"Flutter Deep Linking: Complete Setup Tutorial"},"content":{"rendered":"\n<p>Flutter handles deep linking through a combination of platform-specific configuration (iOS and Android) and Dart-level routing. This guide walks through the complete setup, from native configuration to handling URLs in your Flutter app.<\/p>\n\n\n\n<p>For go_router-specific patterns, see <a href=\"https:\/\/tolinku.com\/blog\/flutter-go-router-deep-links\/\">Flutter go_router Deep Links<\/a>. For a cross-platform overview, see <a href=\"https:\/\/tolinku.com\/blog\/cross-platform-deep-linking-guide\/\">Cross-Platform Deep Linking Guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Flutter Deep Linking Works<\/h2>\n\n\n\n<p>Flutter&#39;s deep linking flow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>The OS receives a link tap and checks if an app is registered to handle it<\/li>\n<li>If your app is registered, the OS launches it (or brings it to the foreground)<\/li>\n<li>Flutter&#39;s engine receives the URL from the platform channel<\/li>\n<li>Your router (go_router, auto_route, or the built-in <code>Router<\/code>) matches the URL to a route<\/li>\n<li>The matched screen is displayed<\/li>\n<\/ol>\n\n\n\n<p>Flutter 3.x+ has built-in deep linking support through the <code>Router<\/code> widget and <code>RouteInformationParser<\/code>. The most popular routing package for deep linking is <a href=\"https:\/\/pub.dev\/packages\/go_router\" rel=\"nofollow noopener\" target=\"_blank\">go_router<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: iOS Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Associated Domains<\/h3>\n\n\n\n<p>Open <code>ios\/Runner.xcworkspace<\/code> in Xcode.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Select the Runner target<\/li>\n<li>Go to Signing &amp; Capabilities<\/li>\n<li>Add the &quot;Associated Domains&quot; capability<\/li>\n<li>Add <code>applinks:go.yourapp.com<\/code><\/li>\n<\/ol>\n\n\n\n<h3 class=\"wp-block-heading\">Info.plist (Custom URL Scheme, Optional)<\/h3>\n\n\n\n<p>If you also want to support custom URL schemes, add to <code>ios\/Runner\/Info.plist<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;key&gt;CFBundleURLTypes&lt;\/key&gt;\n&lt;array&gt;\n  &lt;dict&gt;\n    &lt;key&gt;CFBundleURLSchemes&lt;\/key&gt;\n    &lt;array&gt;\n      &lt;string&gt;yourapp&lt;\/string&gt;\n    &lt;\/array&gt;\n    &lt;key&gt;CFBundleURLName&lt;\/key&gt;\n    &lt;string&gt;com.yourapp&lt;\/string&gt;\n  &lt;\/dict&gt;\n&lt;\/array&gt;\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">FlutterDeepLinkingEnabled<\/h3>\n\n\n\n<p>Add this to your <code>Info.plist<\/code> to enable Flutter&#39;s built-in deep link handling:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;key&gt;FlutterDeepLinkingEnabled&lt;\/key&gt;\n&lt;true\/&gt;\n<\/code><\/pre>\n\n\n\n<p>This tells the Flutter engine to automatically forward incoming URLs to your Dart router. Without this, you&#39;d need to write platform channel code to pass URLs from the native side to Dart.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Android Configuration<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">AndroidManifest.xml<\/h3>\n\n\n\n<p>Add intent filters to your main activity in <code>android\/app\/src\/main\/AndroidManifest.xml<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;activity\n  android:name=&quot;.MainActivity&quot;\n  android:launchMode=&quot;singleTop&quot;&gt;\n\n  &lt;!-- Deep Links (App Links) --&gt;\n  &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot; android:host=&quot;go.yourapp.com&quot; \/&gt;\n  &lt;\/intent-filter&gt;\n\n  &lt;!-- Existing metadata... --&gt;\n  &lt;meta-data\n    android:name=&quot;flutter_deeplinking_enabled&quot;\n    android:value=&quot;true&quot; \/&gt;\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<p>The <code>flutter_deeplinking_enabled<\/code> metadata is the Android equivalent of the iOS <code>FlutterDeepLinkingEnabled<\/code> plist entry.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Digital Asset Links<\/h3>\n\n\n\n<p>Ensure your domain serves a valid <code>assetlinks.json<\/code> at <code>https:\/\/go.yourapp.com\/.well-known\/assetlinks.json<\/code>. If using Tolinku, this is configured automatically in your Appspace settings.<\/p>\n\n\n\n<p>Get your SHA-256 fingerprint:<\/p>\n\n\n\n<pre><code class=\"language-bash\">keytool -list -v -keystore ~\/.android\/debug.keystore -alias androiddebugkey\n<\/code><\/pre>\n\n\n\n<p>For release builds with Google Play App Signing, get the fingerprint from Play Console (Setup &gt; App signing).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Flutter Routing with go_router<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Install go_router<\/h3>\n\n\n\n<pre><code class=\"language-yaml\"># pubspec.yaml\ndependencies:\n  go_router: ^14.0.0\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Define Routes<\/h3>\n\n\n\n<pre><code class=\"language-dart\">import &#39;package:go_router\/go_router.dart&#39;;\n\nfinal router = GoRouter(\n  routes: [\n    GoRoute(\n      path: &#39;\/&#39;,\n      builder: (context, state) =&gt; const HomeScreen(),\n    ),\n    GoRoute(\n      path: &#39;\/product\/:id&#39;,\n      builder: (context, state) {\n        final productId = state.pathParameters[&#39;id&#39;]!;\n        return ProductScreen(id: productId);\n      },\n    ),\n    GoRoute(\n      path: &#39;\/invite\/:code&#39;,\n      builder: (context, state) {\n        final code = state.pathParameters[&#39;code&#39;]!;\n        return InviteScreen(code: code);\n      },\n    ),\n    GoRoute(\n      path: &#39;\/promo\/:slug&#39;,\n      builder: (context, state) {\n        final slug = state.pathParameters[&#39;slug&#39;]!;\n        return PromoScreen(slug: slug);\n      },\n    ),\n  ],\n  errorBuilder: (context, state) =&gt; const NotFoundScreen(),\n);\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Use the Router<\/h3>\n\n\n\n<pre><code class=\"language-dart\">class MyApp extends StatelessWidget {\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp.router(\n      routerConfig: router,\n    );\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>When a deep link arrives (e.g., <code>https:\/\/go.yourapp.com\/product\/456<\/code>), go_router automatically matches the path <code>\/product\/456<\/code> to the <code>ProductScreen<\/code> route and extracts <code>456<\/code> as the <code>id<\/code> parameter.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Handling Query Parameters<\/h3>\n\n\n\n<p>For links with query parameters like <code>https:\/\/go.yourapp.com\/search?q=shoes&amp;sort=price<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-dart\">GoRoute(\n  path: &#39;\/search&#39;,\n  builder: (context, state) {\n    final query = state.uri.queryParameters[&#39;q&#39;] ?? &#39;&#39;;\n    final sort = state.uri.queryParameters[&#39;sort&#39;] ?? &#39;relevance&#39;;\n    return SearchScreen(query: query, sort: sort);\n  },\n),\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Handling the Full URL<\/h2>\n\n\n\n<p>By default, Flutter&#39;s deep link integration strips the scheme and host from the URL, passing only the path to your router. So <code>https:\/\/go.yourapp.com\/product\/123<\/code> becomes <code>\/product\/123<\/code>.<\/p>\n\n\n\n<p>If you need the full URL (for example, to distinguish between different domains), you can use the <code>app_links<\/code> package:<\/p>\n\n\n\n<pre><code class=\"language-yaml\">dependencies:\n  app_links: ^6.0.0\n<\/code><\/pre>\n\n\n\n<pre><code class=\"language-dart\">import &#39;package:app_links\/app_links.dart&#39;;\n\nclass DeepLinkHandler {\n  final AppLinks _appLinks = AppLinks();\n\n  void init() {\n    \/\/ Listen for links while app is running\n    _appLinks.uriLinkStream.listen((Uri uri) {\n      handleLink(uri);\n    });\n\n    \/\/ Check for link that launched the app\n    _appLinks.getInitialLink().then((Uri? uri) {\n      if (uri != null) {\n        handleLink(uri);\n      }\n    });\n  }\n\n  void handleLink(Uri uri) {\n    \/\/ Full URL access: uri.host, uri.path, uri.queryParameters\n    if (uri.host == &#39;go.yourapp.com&#39;) {\n      router.go(uri.path);\n    }\n  }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Testing<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">iOS Testing<\/h3>\n\n\n\n<p>On a physical device:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Paste <code>https:\/\/go.yourapp.com\/product\/123<\/code> in Notes and tap it<\/li>\n<li>Send the link via iMessage and tap it<\/li>\n<li>Verify the app opens and displays the product screen<\/li>\n<\/ol>\n\n\n\n<p>To test during development without a configured domain, use the custom URL scheme:<\/p>\n\n\n\n<pre><code>yourapp:\/\/product\/123\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Android Testing<\/h3>\n\n\n\n<p>Use ADB:<\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell am start -a android.intent.action.VIEW \\\n  -d &quot;https:\/\/go.yourapp.com\/product\/123&quot; \\\n  com.yourapp\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Flutter DevTools<\/h3>\n\n\n\n<p>Flutter DevTools shows the current route state. Open DevTools, navigate to the &quot;Router&quot; tab, and trigger a deep link. You&#39;ll see the URL being parsed and the matched route.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Debug Logging<\/h3>\n\n\n\n<p>Add logging to your router to trace deep link handling:<\/p>\n\n\n\n<pre><code class=\"language-dart\">final router = GoRouter(\n  debugLogDiagnostics: true, \/\/ Logs route matching\n  routes: [\n    \/\/ ...\n  ],\n);\n<\/code><\/pre>\n\n\n\n<p>This prints route matching information to the console, which is useful for debugging why a link isn&#39;t matching the expected route.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Common Issues<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Links Open in Browser Instead of App<\/h3>\n\n\n\n<p><strong>iOS<\/strong>: Verify that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The Associated Domains entitlement exactly matches your domain (including subdomain)<\/li>\n<li>The AASA file is valid and accessible<\/li>\n<li>You deleted and reinstalled the app after adding the entitlement (iOS caches AASA on install)<\/li>\n<li><code>FlutterDeepLinkingEnabled<\/code> is set to <code>true<\/code> in Info.plist<\/li>\n<\/ul>\n\n\n\n<p><strong>Android<\/strong>: Verify that:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The intent filter has <code>android:autoVerify=&quot;true&quot;<\/code><\/li>\n<li>The assetlinks.json file is valid and accessible<\/li>\n<li>The SHA-256 fingerprint matches your signing key<\/li>\n<li><code>flutter_deeplinking_enabled<\/code> metadata is set to <code>true<\/code><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Route Not Matched<\/h3>\n\n\n\n<p>If the app opens but shows the wrong screen (or the error\/404 screen):<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Enable <code>debugLogDiagnostics<\/code> on your GoRouter<\/li>\n<li>Check that the path pattern matches the incoming URL<\/li>\n<li>Remember that the scheme and host are stripped; only the path is matched<\/li>\n<li>Check for trailing slashes: <code>\/product\/123<\/code> and <code>\/product\/123\/<\/code> may be different routes<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Cold Start Not Working<\/h3>\n\n\n\n<p>If deep links work when the app is backgrounded but not when it&#39;s fully closed:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>On iOS, verify the <code>FlutterDeepLinkingEnabled<\/code> plist key<\/li>\n<li>On Android, verify the <code>flutter_deeplinking_enabled<\/code> metadata<\/li>\n<li>If using <code>app_links<\/code>, ensure <code>getInitialLink()<\/code> is called before any navigation occurs<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Production Checklist<\/h2>\n\n\n\n<ul class=\"checklist wp-block-list\"><li><input type=\"checkbox\" disabled> Associated Domains entitlement configured in Xcode<\/li><li><input type=\"checkbox\" disabled> <code>FlutterDeepLinkingEnabled<\/code> set to <code>true<\/code> in Info.plist<\/li><li><input type=\"checkbox\" disabled> AASA file accessible and valid<\/li><li><input type=\"checkbox\" disabled> Android intent filters added with <code>autoVerify=\"true\"<\/code><\/li><li><input type=\"checkbox\" disabled> <code>flutter_deeplinking_enabled<\/code> metadata set to <code>true<\/code><\/li><li><input type=\"checkbox\" disabled> assetlinks.json accessible and valid (both debug and release fingerprints)<\/li><li><input type=\"checkbox\" disabled> go_router routes match all deep link paths<\/li><li><input type=\"checkbox\" disabled> Error\/404 route handles unmatched paths gracefully<\/li><li><input type=\"checkbox\" disabled> Tested on physical iOS device<\/li><li><input type=\"checkbox\" disabled> Tested on physical Android device<\/li><li><input type=\"checkbox\" disabled> Cold start deep links work on both platforms<\/li><\/ul>\n\n\n\n<p>For integrating with the Tolinku SDK, see the <a href=\"https:\/\/tolinku.com\/docs\/developer\/sdks\/flutter\/\">Flutter SDK documentation<\/a>. For advanced routing patterns, see <a href=\"https:\/\/tolinku.com\/blog\/deep-link-routing-guide\/\">Deep Link Routing Guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Implement deep linking in Flutter apps. Configure go_router, handle platform-specific setup, and test deep links across iOS and Android.<\/p>\n","protected":false},"author":2,"featured_media":886,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Flutter Deep Linking: Complete Setup Tutorial","rank_math_description":"Implement deep linking in Flutter apps. Configure go_router, handle platform-specific setup, and test deep links across iOS and Android.","rank_math_focus_keyword":"Flutter deep linking","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-flutter-deep-linking-setup.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-flutter-deep-linking-setup.png","footnotes":""},"categories":[15],"tags":[25,23,179,20,57,180,24,69,22],"class_list":["post-887","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-android","tag-app-links","tag-dart","tag-deep-linking","tag-flutter","tag-go-router","tag-ios","tag-mobile-development","tag-universal-links"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/887","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=887"}],"version-history":[{"count":3,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/887\/revisions"}],"predecessor-version":[{"id":2522,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/887\/revisions\/2522"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/886"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=887"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=887"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=887"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}