Modern mobile applications often need to take users directly to specific content instead of making them navigate through multiple screens. This is where deep linking becomes useful.
For example, suppose your Flutter app contains a product with ID 123. Instead of asking users to open the app, search for the product, and then open it, you can provide a URL such as:
https://myapp.com/product/123When the user taps the link, the application can open directly on the product details screen.
Deep linking is useful for product pages, user profiles, articles, promotional campaigns, notifications, email links, and shared content.
In this article, we’ll look at how deep linking works in Flutter and how to configure it for Android and iOS.
What Is Deep Linking?
A deep link is a URL that directs a user to a specific location inside an application. For more technical details, see the official Flutter deep linking documentation.
Without deep linking, a user may need to:
- Open the application.
- Navigate to the required section.
- Search for the content.
- Open the desired page.
With a deep link, the same destination can be opened directly.
For example:
https://myapp.com/product/123This URL could open the profile screen for user 25.
Deep links are commonly used for:
- Product details
- User profiles
- Blog articles
- Orders
- Password reset
- Email verification
- Push notifications
- Marketing campaigns
- Shared application content
Types of Deep Links
There are two common approaches to deep linking.
Custom URL Schemes
A custom scheme uses a URL such as:
myapp.com/product/123Here, my app is a scheme registered by the application.
This approach is relatively easy to configure, but custom schemes don’t provide the same domain-based verification as HTTPS links.
Android App Links and iOS Universal Links
A more robust approach is to use HTTPS URLs:
https://myapp.com/product/123Android Apps can use App Links to associate HTTPS URLs with specific content inside the application, while iOS uses Universal Links.
One advantage is that the same URL can work on the website and open the application when it is installed.
For production applications, HTTPS-based links are generally useful when you control the associated domain.
How Deep Linking Works
The process can be understood as:
User taps URL
↓
Operating System
↓
Android App Link / iOS Universal Link
↓
Flutter Application
↓
Flutter Router
↓
Target Screen For example:
https://myapp.com/product/123
The operating system identifies the application associated with the domain. Flutter then receives the route and navigates to the appropriate screen, creating a more direct user experience.
Step 1: Create a Flutter Project
If you don’t already have a project, create one:
flutter create deep_link_demo
cd deep_link_demo
flutter runFor this example, we’ll use a home screen and a product details screen.
First, define a simple route structure:
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/product': (context) => const ProductScreen(),
},
);This gives the application a basic navigation structure.
For applications with dynamic URLs such as /product/123, a routing package can provide a cleaner implementation.
Step 2: Define Your URL Structure
Before implementing deep linking, decide how your URLs should look.
For example:
https://myapp.com/
https://myapp.com/product/123
https://myapp.com/profile/25
https://myapp.com/article/flutter-deep-linkingA consistent structure makes routing easier to maintain.
For product pages, you might use:
/product/123
/product/456
/product/789The number after /product/ represents the product ID.
Step 3: Configure Android App Links
For Android, open:
android/app/src/main/AndroidManifest.xmlInside the activity declaration, add an intent filter:
<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"
android:host="myapp.com" />
</intent-filter>Replace myapp.com with your actual domain.
The android:autoVerify=”true” setting allows Android to verify that your application is associated with the domain.
Configure assetlinks.json
Your website also needs an association file.
Create:
https://myapp.com/.well-known/assetlinks.jsonA basic example is:
[
{
"relation": [
"delegate_permission/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "com.example.deep_link_demo",
"sha256_cert_fingerprints": [
"YOUR_SHA256_CERTIFICATE_FINGERPRINT"
]
}
}
]Replace the package name and certificate fingerprint with your application’s actual values.
Step 4: Configure iOS Universal Links
For iOS, open the Flutter iOS project in Xcode.
Go to:
Signing & CapabilitiesAdd the Associated Domains capability.
Then add:
applinks:myapp.comReplace the domain with your actual website.
Configure apple-app-site-association
Your website also needs an Apple App Site Association file at:
https://myapp.com/.well-known/apple-app-site-associationFor example:
{
"applinks": {
"details": [
{
"appIDs": [
"TEAM ID.com.example.deep_link_demo"
],
"components": [
{
"/": "/product/*"
}
]
}
]
}
}
The TEAM_ID should be replaced with your Apple Developer Team ID, and the bundle identifier should match your application.
The /product/* rule allows product URLs to open the application.
Step 5: Handle Deep Links in Flutter
For applications with dynamic routes, go_router is a convenient option.
Install it using:
flutter pub add go_routerThen define your routes:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) {
return const HomeScreen();
},
),
GoRoute(
path: '/product/:id',
builder: (context, state) {
final productId = state.pathParameters['id'];
return ProductScreen(
productId: productId!,
);
},
),
],
);
Use the router with MaterialApp.router:
MaterialApp.router(
routerConfig: router,
);
The product screen can receive the ID:
class ProductScreen extends StatelessWidget {
final String productId;
const ProductScreen({
super.key,
required this.productId,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Product Detail'),
),
body: Center(
child: Text('Product ID: $productId'),
),
);
}
}
Now the URL:
https://myapp.com/product/123can be mapped to:
/product/123and Flutter can extract 123 as the product ID.
Handling Query Parameters
Deep links can also contain query parameters.
For example:
https://myapp.com/product/123?ref=emailHere, 123 is the path parameter and ref=email is a query parameter.
With go_router, you can access it using:
final source = state.uri.queryParameters['ref'];This can be useful for tracking campaigns or identifying where users came from.
Testing Deep Links
Testing is an important part of implementing deep linking.
Test the following scenarios:
Application Installed
Open:
https://myapp.com/product/123The application should open the product screen.
Application Closed
Completely close the application and open the link. Verify that the correct screen appears after startup.
Application Running
Keep the application open and trigger another deep link. Make sure it navigates to the new destination correctly.
Application Not Installed
Open the link on a device without the application. The user should be directed to your website or another appropriate fallback.
Invalid URL
Test URLs with invalid IDs or unsupported paths and make sure the application handles them gracefully instead of crashing.
Common Deep Linking Issues
Deep linking may fail even when the Flutter routing code is correct. Some common causes include:
Incorrect domain: Make sure the domain in your Android and iOS configuration matches your actual domain.
Wrong certificate fingerprint: Android App Links require the correct SHA-256 certificate fingerprint. Debug and release builds can use different signing certificates.
Incorrect Team ID: The Apple App Site Association file must contain the correct Team ID and bundle identifier.
Incorrect file location: Both assetlinks.json and apple-app-site-association must be available from the .well-known directory.
Missing route: The Flutter router must have a route matching the incoming URL.
Invalid parameters: Always validate IDs and other parameters received through URLs before using them.
Deep Linking Best Practices
A reliable deep-linking implementation should follow a few basic principles:
- Keep URL structures simple and meaningful.
- Use HTTPS-based links for production applications when appropriate.
- Validate all parameters received from URLs.
- Provide a fallback for invalid or unavailable content.
- Test both Android and iOS devices.
- Test cold-start and background scenarios.
- Test release builds, not just debug builds.
- Keep routing logic separate from business logic.
- Make sure website association files are correctly configured.
Conclusion
Deep linking provides a direct connection between external URLs and specific screens within a Flutter application. It can significantly reduce the number of steps users need to take when accessing shared content, products, profiles, orders, or promotional pages.
A complete implementation involves three main areas:
- Platform configuration – Android App Links and iOS Universal Links.
- Domain verification – assetlinks.json for Android and the Apple App Site Association file for iOS.
- Flutter routing – converting incoming URLs into application routes and handling their parameters.
For simple applications, Flutter’s built-in routing may be enough. For applications with multiple dynamic URLs and complex navigation, a dedicated routing solution such as go_router can make the implementation easier to manage.
Once the platform configuration and Flutter routing are correctly connected, deep links provide a smooth way to take users directly from a URL to the exact content they need.