Generate Flutter deep linking config
Enter your domain and the routes that should open from a link. We write the go_router routes, the iOS Info.plist and entitlements, and the Android manifest with the flutter_deeplinking_enabled flag.
Your config
Each file shows where it goes in your project.
router.dart
lib/router.dart (Replace the screen imports with your own screen files.)
import 'package:go_router/go_router.dart';
// Replace these imports with your own screen files.
import 'screens/product_screen.dart';
import 'screens/cart_screen.dart';
import 'screens/home_screen.dart';
final GoRouter router = GoRouter(
initialLocation: '/',
routes: <RouteBase>[
GoRoute(
path: '/product/:id',
name: 'product',
builder: (context, state) => ProductScreen(id: state.pathParameters['id']!),
),
GoRoute(
path: '/cart',
name: 'cart',
builder: (context, state) => const CartScreen(),
),
GoRoute(
path: '/',
name: 'home',
builder: (context, state) => const HomeScreen(),
),
],
);main.dart
lib/main.dart
import 'package:flutter/material.dart';
import 'router.dart';
void main() => runApp(MaterialApp.router(routerConfig: router));Info-deep-linking.xml
ios/Runner/Info.plist (Add inside the top-level dict. Flutter 3.27 and later turn this on by default. Setting it keeps older versions in step.)
<key>FlutterDeepLinkingEnabled</key>
<true/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>Runner.entitlements
ios/Runner/Runner.entitlements (Or add Associated Domains in Xcode under Signing & Capabilities. Set the file as the target's entitlements.)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:links.example.com</string>
</array>
</dict>
</plist>AndroidManifest-deep-linking.xml
android/app/src/main/AndroidManifest.xml (Add inside <activity android:name=".MainActivity">. Flutter 3.27 and later turn this on by default. Setting it keeps older versions in step.)
<meta-data android:name="flutter_deeplinking_enabled" android:value="true" />
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
<data android:host="links.example.com" />
<data android:pathPrefix="/product/" />
<data android:path="/cart" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>Now generate the files your domain serves
The app config opens your app only after links.example.com serves an AASA file and an assetlinks.json file that name it. Both generators open with this app filled in.
Already serving the files?
The deep link checker fetches both files from links.example.com and reports what a device would read.
How it works
- 1. Pick your router
- go_router is the default. A plain Navigator reads the link in onGenerateRoute.
- 2. Enter your link domains
- Each host becomes an Associated Domains entry on iOS and an intent filter host on Android.
- 3. Add your routes
- Write /product/:id -> product for each screen. A name such as product opens ProductScreen.
- 4. Choose who handles links
- Leave Flutter's handler on, or switch it off when a plugin or SDK processes links itself.
Reference
The fields
Only the domain and the routes are required. The identifiers prefill the domain-file generators.
| Field | What to enter and where to find it |
|---|---|
| Router | go_router writes GoRoute entries and a MaterialApp.router. Navigator writes an onGenerateRoute function that matches path segments. |
| flutter_deeplinking_enabled | true lets Flutter pass each link to your router, which is the default since Flutter 3.27. false turns Flutter's handler off so a plugin or SDK receives the link. |
| Link domains | The hosts that serve your links, one per line, such as links.example.com. No https:// and no path. |
| Routes | One /path -> name per line. :id is a parameter. Wildcards are not supported, so list each path. Optional :id? works with go_router only, which gets one route without the value and one with it. A route for / is added when you list none, because the app opens there. |
| Custom URL scheme | Optional. The part before ://, in lowercase. It is registered in Info.plist and the manifest and is not verified. |
| iOS bundle ID | Optional. In Xcode under Signing & Capabilities, or PRODUCT_BUNDLE_IDENTIFIER in the project. |
| App ID prefix | Optional. Ten characters, usually your Team ID under Membership details in your Apple Developer account. |
| Android package name | Optional. The applicationId in android/app/build.gradle. |
Versions these snippets target
| Tool | Version |
|---|---|
| Flutter | 3.27 and later, where the deep link flag defaults to on. The generator writes the value explicitly, so older versions that need an opt-in also work. |
| go_router | 7 and later, which read path parameters from state.pathParameters. Checked against the 18.x API. |
| iOS | Universal Links through the Associated Domains capability on the Runner target. |
| Android | App Links with android:autoVerify, which Android 6.0 and later check against assetlinks.json. |
When a link does not open the right screen
| What you see | Likely cause and fix |
|---|---|
| Page not found or an unknown route | The path has no matching route. A short link such as /abc123 needs a route that handles it, or the router shows its error page. |
| A plugin or SDK never receives the link | Flutter's handler is on and took the link first. Set the flag to false in Info.plist and in the manifest. |
| The app opens but the router ignores the link | With the flag off, Flutter no longer routes links. Call router.go() with the link your SDK or plugin delivers. |
| iOS opens Safari, not the app | The entitlement is missing or not attached to the Runner target, or the AASA file does not list your App ID. Check the domain with the deep link checker. |
| Android shows a chooser | Verification failed. assetlinks.json is missing, is served through a redirect, or lists a different package or SHA-256 certificate fingerprint. |
| The flag has no effect on Android | The meta-data element must sit inside the activity element, next to the intent filters, not inside application. |
Need deferred deep links and install attribution?
The config on this page opens a screen when the app is installed and a person taps a link. It cannot help the person who taps a link before installing: they go to the store, then open the app with no link in hand.
Deferred deep links, which carry that link through the install, and install attribution need an SDK. The WarpLink Flutter SDK delivers the original link on first open and records which link led to the install. It takes the flag set to false, and its onLink callback then drives your router.
What a Flutter app needs for links
Flutter handles incoming links itself. With go_router, the route paths you declare are matched against the URL the system hands to the app, so one set of routes serves a link, a back stack and a web build. Flutter 3.27 turned this handler on by default.
The platforms still need their own settings. iOS needs the Associated Domains entitlement for each host. Android needs an intent filter with android:autoVerify. Your domain serves the apple-app-site-association and assetlinks.json files that both platforms check, and the generators for those files are linked next to your output. Test on a signed build after each change.
Frequently asked questions
Does go_router handle incoming URLs?
Yes. With Flutter's built-in deep link handling on, go_router receives the URL and matches it against your route paths, including path parameters such as :id.
When do I change Info.plist?
To control Flutter's own link handling with the FlutterDeepLinkingEnabled key, and to register a custom URL scheme. The Associated Domains entitlement is a separate file, one entry for each host.
What does flutter_deeplinking_enabled do?
It decides whether Flutter's handler passes incoming links to your router. It has defaulted to on since Flutter 3.27. Set it to false in Info.plist and in the manifest when a plugin or SDK handles links.
Can a plugin conflict with Flutter's handler?
Yes. With the handler on, Flutter routes each link itself, which can stop a plugin that also listens for links from seeing it. Use one handler. Turn the flag off for a plugin, or leave it on and use go_router.
Should I use go_router or Navigator?
go_router matches URLs, reads path parameters and keeps the back stack in step with the link. A plain Navigator works too: Flutter hands the path to onGenerateRoute, and you match the segments yourself.
How do I test cold starts?
Close the app, then open a link from Notes on iOS or with adb shell am start on Android. Repeat with the app in the background to test the warm path.
Does the domain file replace native settings?
No. The domain files prove your domain trusts the app. The entitlement, Info.plist and manifest settings tell each platform that the app accepts the links.
Related tools
- AASA generatorBuild the apple-app-site-association file for Universal Links from your Team ID, bundle ID and path rules.
- assetlinks.json generatorBuild the Digital Asset Links file for Android App Links from your package name and signing certificate fingerprints.
- Deep link checkerOne report for iOS and Android. We test the AASA file, assetlinks.json, redirects, headers and the paths you list.
We handle the link layer for you
Add the WarpLink Flutter SDK for deep links, deferred deep links and install attribution on top of this config.