Ever clicked a link and ended up exactly where you wanted to go? That's deep linking magic!
Imagine your friend finds a cool event on your app and wants to share it. Instead of texting you "Hey, search for Flutter Conference 2025 in that app," they just send a link. You tap it, and boom – you're looking at that exact event. No searching, no scrolling, no frustration.
That's what we're building today.
Why Deep Links Matter
Deep links are like shortcuts to specific parts of your app. They make your app feel connected to the rest of the digital world, not like a lonely island.
Here's what users get:
- Instant access to shared content
- No hunting through menus
- Smooth experience from web to app
Here's what you get:
- Better user experience (happy users stick around)
- More effective marketing (links go straight to products)
- Higher engagement (less friction = more action)
How Deep Links Work
Every deep link has parts, like a home address:
https://myapp.com/event/flutter-conference-2025
Breaking it down:
-
https://
- How to deliver the link -
myapp.com
- Your app's address -
/event/flutter-conference-2025
- The specific page
You can also add extras:
-
?category=tech&date=2025
- Filters and options -
#speakers
- Jump to a specific section
Two Ways to Make Deep Links
Option 1: Custom Schemes (Easy Start)
myapp://event/flutter-conference
Good: Quick to set up, works without a website
Bad: Any app can steal your scheme, looks unprofessional
Option 2: HTTPS Links (Professional)
https://myapp.com/event/flutter-conference
Good: Secure, falls back to website, looks professional
Bad: Requires owning a domain, bit more setup
We recommend HTTPS links for real apps.
Setting Up Android
Step 1: Tell Android About Your Links
Open android/app/src/main/AndroidManifest.xml
and add this inside your <activity>
tag:
<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>
Step 2: Prove You Own Your Domain
Create a file called assetlinks.json
:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.myapp",
"sha256_cert_fingerprints": ["YOUR_SHA256_FINGERPRINT"]
}
}
]
Upload it to: https://myapp.com/.well-known/assetlinks.json
Step 3: Test It
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://myapp.com/event/flutter-conference" \
com.example.myapp
Setting Up iOS
Step 1: Add Associated Domains
- Open
ios/Runner.xcworkspace
in Xcode - Select Runner → Signing & Capabilities
- Add "Associated Domains" capability
- Add:
applinks:myapp.com
Step 2: Create Apple's Verification File
Create apple-app-site-association
(no file extension):
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.example.myapp",
"paths": ["/event/*", "/events"]
}
]
}
}
Upload to: https://myapp.com/.well-known/apple-app-site-association
Step 3: Test on iOS
xcrun simctl openurl booted "https://myapp.com/event/flutter-conference"
Flutter Code: Making It Work
Set Up GoRouter
import 'package:go_router/go_router.dart';
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/events',
builder: (context, state) => EventsPage(),
),
GoRoute(
path: '/event/:eventId',
builder: (context, state) {
final eventId = state.pathParameters['eventId']!;
return EventDetailsPage(eventId: eventId);
},
),
],
);
Connect to Your App
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'My App',
routerConfig: router,
);
}
}
Handle Missing Content
What if someone shares a link to an event that doesn't exist? Handle it gracefully:
class EventDetailsPage extends StatelessWidget {
final String eventId;
EventDetailsPage({required this.eventId});
@override
Widget build(BuildContext context) {
// Try to find the event
final event = findEventById(eventId);
if (event == null) {
return Scaffold(
appBar: AppBar(title: Text('Oops!')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.event_busy, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text('Event Not Found',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
Text('This event might have been moved or cancelled.'),
SizedBox(height: 16),
ElevatedButton(
onPressed: () => context.go('/events'),
child: Text('Browse All Events'),
),
],
),
),
);
}
// Show the event details
return Scaffold(
appBar: AppBar(title: Text(event.title)),
body: Column(
children: [
// Event details here
],
),
);
}
}
Adding Login Protection
Want to protect certain pages? Easy:
final router = GoRouter(
redirect: (context, state) {
final isLoggedIn = checkIfUserIsLoggedIn();
final isLoginPage = state.matchedLocation == '/login';
// Not logged in? Send to login
if (!isLoggedIn && !isLoginPage) {
return '/login?redirect=${state.matchedLocation}';
}
// Already logged in? Skip login page
if (isLoggedIn && isLoginPage) {
final redirectUrl = state.uri.queryParameters['redirect'];
return redirectUrl ?? '/';
}
return null; // No redirect needed
},
routes: [
// Your routes here
],
);
Testing Your Links
Simple Tests
Android:
adb shell am start -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://myapp.com/event/flutter-conference" \
com.example.myapp
iOS:
xcrun simctl openurl booted "https://myapp.com/event/flutter-conference"
Real-World Tests
- Send the link via WhatsApp or Messages
- Create a simple webpage with your link
- Share on social media and test
- Test with and without your app installed
Common Problems and Fixes
Problem: App opens inside another app
Fix: Add android:launchMode="singleTask"
to your activity in AndroidManifest.xml
Problem: Link opens in browser instead of app
Fix: Check your domain verification files are uploaded correctly
Problem: Multiple apps claim the same URL
Fix: Make sure android:autoVerify="true"
is set and your verification files are correct
Pro Tips
Keep URLs simple:
myapp.com/event/flutter-summit
is better thanmyapp.com/e/fs2025/details
Plan your structure: Think about how users will share and remember links
Handle errors gracefully: Always plan for missing content and network issues
Test everything: Test on different devices, with and without your app installed
Track performance: Use analytics to see how users interact with your links
Demo: See It in Action
Let's walk through the complete deep linking experience step by step:
1. Our App's Home Screen
First, here's what our EventSpot app looks like when users open it normally:
2. Events List Screen
Users can browse all available events from our events screen:
3. Deep Link in Action - Browser Entry
Now here's the magic! When someone shares a deep link, users can enter it directly in their browser:
4. App Permission Request
iOS asks for permission to open the link in our app instead of staying in the browser:
5. Direct Navigation Success!
And voilà! The user lands directly on the specific event details screen, skipping all the navigation:
6. Demo Video!
This seamless transition from web link to specific app content is what makes deep linking so powerful for user experience!
Source Code and Resources
Get the complete source code:
Additional Resources:
- Flutter Deep Linking Documentation
- GoRouter Package
- Android App Links Guide
- iOS Universal Links Guide
Wrapping Up
Deep linking transforms your Flutter app from an isolated island into a connected part of the digital world. Users can share content effortlessly, your marketing becomes more effective, and the overall experience gets much better.
Questions? Found a bug? Let me know in the comments below!
Top comments (3)
This is very handy! @faidterence thanks for sharing
Helpful @faidterence , a live demo would be better !
Hi @code_with_random_1340 It's added, Thanks!