Migrating Firebase Dynamic Links to App Links has become a mandatory step for Flutter developers in 2025. Firebase Dynamic Links (FDL) was a convenient solution to deep linking, which could route a single “smart” URL to different destinations based on platform and installed app status. Google, however, has deprecated Firebase Dynamic Links and intends to end support on August 25, 2025, following which all FDL links will stop functioning. This puts a requirement on Flutter developers to migrate their apps to a different deep linking approach before that date in order to ensure their links remain functional.
The recommended replacement is to use the native deep link systems: App Links on Android and Universal Links on iOS. These work with normal HTTPS URLs on a domain we control. No third party, no surprise shutdowns, and no extra cost.
Table of Contents
App Links vs FDL: What we lose and how to replace it
1. No automatic “store → continue” flow (deferred deep link).
Native links open our app if installed; if not installed they open our website. To approximate FDL’s “continue after install,” We can use custom clipboard approach.
2. No built-in short links & analytics.
Use our own domain structure (e.g., ourapp.com/invite/ABC123) with web analytics, or adopt a third-party managed dashboards (Branch/AppsFlyer/etc.).
3. A bit more setup.
We’ll host assetlinks.json (Android) and apple-app-site-association (iOS) under /.well-known/ on our domain. Firebase’s migration guide explicitly allows Firebase Hosting or GitHub Pages if we want a quick host.
A Guide to Migrating from Firebase Dynamic Links to App Links
Step 1: Prepare a domain and host association files
Pick a domain or subdomain we control. Serve these files over HTTPS:
Android: https://<our-domain>/.well-known/assetlinks.json
Use the release signing key fingerprint. If we use Play App Signing, get the App Signing key from Play Console.
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": “com.example.ourapp",
"sha256_cert_fingerprints": ["00:11:22:33:...:FF"]
}
}]iOS: https://<our-domain>/.well-known/apple-app-site-association (no file extension)appIDs is TeamID.BundleID. We can restrict paths later.
{
"applinks": {
"apps": [],
"details": [{
"appIDs": ["ABCDE12345.com.example.ourapp"],
"paths": ["*"]
}]
}
}For Android, With Google Digital Asset Links, Use the Statement List Generator & Tester to generate and validate our assetlinks.json. Fill in the form, then click Generate/Test statement.
For iOS, Ensure the AASA file is reachable over HTTPS, served with the correct Content-Type (application/json), and no redirects.
Reference examples (Facebook) :
https://facebook.com/.well-known/apple-app-site-association
https://facebook.com/.well-known/assetlinks.json
Step 2: Configure Android App Links
Add an intent filter in android/app/src/main/AndroidManifest.xml under main activity:
<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"
android:pathPrefix="/" />
</intent-filter>Notes:
android:autoVerify="true"lets Android verify our domain against assetlinks.json and open our app by default without a chooser.- The package name and signing fingerprint must match what we published in assetlinks.json. Debug and release keys are different.
Step 3: Configure iOS Universal Links
In Xcode, open the Runner target. Under Signing and Capabilities, add Associated Domains:
applinks:ourapp.comIf we use app_links package, disable Flutter’s built-in deep link handler to avoid conflicts. In ios/Runner/Info.plist:
<key>FlutterDeepLinkingEnabled</key>
<false/>Step 4: Handle links in Flutter with app_links
Add the package :
dependencies:
flutter:
sdk: flutter
app_links: ^6.4.1Initialize early so we catch cold starts. Listen for the initial link and the stream:
import 'package:flutter/material.dart';
import 'package:app_links/app_links.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final AppLinks _appLinks;
@override
void initState() {
super.initState();
_appLinks = AppLinks();
// 1) Cold start
_appLinks.getInitialAppLink().then((uri) {
if (uri != null) _handleUri(uri);
});
// 2) Warm state
_appLinks.uriLinkStream.listen((uri) {
if (uri != null) _handleUri(uri);
});
}
void _handleUri(Uri uri) {
final segs = uri.pathSegments;
// Check pattern /invite/:id
if (segs.isNotEmpty && segs.first == 'invite') {
final code = segs.length > 1 ? segs[1] : null;
if (code != null) {
Navigator.of(context).pushNamed('/invite', arguments: code);
}
return;
}
// Add more patterns here, for example /product/:id
}
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'/invite': (c) => const Placeholder(),
},
home: const Scaffold(
body: Center(child: Text('Home')),
),
);
}
}Note :
- Cold Start : the app launches from scratch (not in memory) because the user tapped a link (App/Universal Link), a notification, or an OS intent.
getInitialAppLink()returns the URI that triggered the initial launch. Call it once on startup to capture the first deep link. - Warm State : the app is already running (foreground/background) and stays alive. When the user opens a new link while the app is active, uriLinkStream emits a URI each time an incoming link arrives. Use this to handle deep links without restarting the app (e.g., from a browser or a notification).
Step 5: Rollout, test, and replace old FDL links
Replace public FDL URLs in emails, push templates, QR codes, and social posts with our new HTTPS links. For assets we cannot change, add server redirects where possible.
Conclusion
This migration from Firebase Dynamic Links to App Links is mostly setup work. Claim a domain, publish two small files, enable one capability per platform, then route links in Flutter. We get a durable, standards-based foundation that is free and under our control. If we later need short links, analytics, or deferred deep linking, add them on top with our website, or a deep link provider.
