Replacing Firebase Dynamic Links with App Links and Universal Links in Flutter (2026 Indie Guide)

By gspteck Editorial · Published 2026-10-08 · Last verified 2026-10-08

Firebase Dynamic Links shut down on 2025-08-25. If your Flutter app still has page.link URLs in old emails, QR codes, or share sheets, those links now return 404, and any code that waits for Dynamic Links data simply gets nothing back. Plenty of small apps never finished the migration because the old links “mostly worked” right up to the deadline. This guide is the version we wish we had: what Dynamic Links actually did for you, which pieces you really need to replace, how to serve the two verification files from Firebase Hosting, the Flutter-side setup on Android and iOS, and a test matrix to run before every release. It builds on our Flutter + Firebase shipping checklist and the Firebase Auth hardening guide.

What actually broke, and what you need back

The Firebase Dynamic Links deprecation FAQ spells out the post-shutdown behavior. Clicked links return HTTP 404, the short-link and link-stats APIs return 400 or 403, and first-open attribution calls return empty data rather than crashing. The SDKs don’t crash your app, but your own code has to handle the empty responses.

Dynamic Links bundled several jobs into one product. Split them apart before you pick a replacement:

Be honest about deferred deep linking. For a solo app with a referral flow, a vendor might be worth it. For most utility apps, “open the app if installed, otherwise show a good web page with store buttons” covers nearly every real user.

Three columns splitting the jobs Firebase Dynamic Links used to do: opening the installed app with App Links and Universal Links on your own domain, routing inside the app with Flutter deep links and go_router plus a web fallback page, and post-install deferred deep linking that needs a vendor or a decision to skip it
Split the old Dynamic Links jobs apart: open the app, route inside it, and decide whether you need post-install context.

Don’t forget email links in Firebase Auth

If you use email link sign-in, password reset, or email verification links that open the app, the FAQ notes that these keep working only with updated Firebase Authentication SDKs. Older app versions that still depend on Dynamic Links lose those flows. Follow the current email link authentication guide for Flutter, then raise your minimum supported version if a meaningful share of users is still on old builds. A Remote Config prompt, as in our kill switch and staged rollout guide, is a gentle way to push those users forward.

The two verification files

Both platforms prove that your app and your domain belong together through a JSON file at a fixed path on that domain.

Android: assetlinks.json. Android’s assetlinks configuration guide requires the file at https://yourdomain/.well-known/assetlinks.json, served with content type application/json, over HTTPS, and without any 301 or 302 redirects. Each host your app handles needs its own copy. The file lists your package name and the SHA-256 fingerprint of the signing certificate. This is the most common failure for solo developers. If you use Play App Signing, the fingerprint from your local keystore won’t match what users install. Take the fingerprint, or the ready-made JSON snippet, from Play Console under Release, then Setup, then App signing. Add your debug fingerprint too if you test with debug builds.

iOS: apple-app-site-association. Apple’s Supporting associated domains page says the file has no extension, lives in .well-known, and must be served over HTTPS with a valid certificate and no redirects. It lists your app ID (Team ID plus bundle ID) and the URL paths the app should claim. Since iOS 14, devices don’t fetch the file from your server. Apple’s CDN does, typically within 24 hours, and devices check for updates about once a week after install. A broken file can therefore stay broken on users’ phones for days, so test before you ship.

Four-step flow for the verification files: assetlinks.json and apple-app-site-association under .well-known, served as application/json over HTTPS with no 301 or 302 redirects, using the Play App Signing SHA-256 fingerprint and the Apple Team ID
Two verification files on one domain: correct path, JSON content type, no redirects, and the right signing identity.

Serving the files from Firebase Hosting

Firebase Hosting works well for this, as long as you account for its rules. According to the Hosting configuration reference, Hosting resolves a request in this order: reserved /__/* paths, configured redirects, exact-match static content, configured rewrites, then 404 pages. In practice:

"headers": [{
  "source": "/.well-known/apple-app-site-association",
  "headers": [{"key": "Content-Type", "value": "application/json"}]
}]

The Flutter side

Flutter’s deep linking overview and its cookbooks for Android App Links and iOS Universal Links cover the setup. The parts that trip people up:

Three cards for the Flutter side: Android intent filter with autoVerify and an https host while the deep linking flag is on by default, iOS Associated Domains with applinks entries matching web paths, and a router that maps paths to screens, sends unknown paths to a safe default, and checks sign-in first
The Flutter side: Android intent filter, iOS Associated Domains, and a router that handles unknown paths and sign-in.

A test matrix before every release

  1. Fetch both files with curl -I on the exact host. Expect 200, application/json, and no 3xx.
  2. Re-verify on Android. On Android 12 or later, run adb shell pm verify-app-links --re-verify with your package name, then adb shell pm get-app-links, and look for verified. The verify App Links guide lists the other states.
  3. Cold start. With the app fully closed, tap a link from a notes app or email. It should open the right screen, not the home screen.
  4. Warm start. With the app already open on another screen, tap a link and confirm navigation.
  5. Not installed. Uninstall and tap a link. Your web page should load with useful content and store buttons.
  6. Signed out. Tap a private link while signed out. The app should ask for sign-in, then continue to the linked screen.

Keep a short list of legacy page.link URLs you know are still in circulation, such as old QR codes or onboarding emails, and replace them at the source. They will never resolve again.

Grid of six pre-release link tests: curl both files for 200 JSON with no redirect, confirm verified state with pm get-app-links, cold start, warm start, not installed web fallback, and signed-out login then resume
Run this six-step link test matrix before every release that touches routing or hosting.

Cleanup after the migration

Key takeaways

Sources

More from gspteck: Cloud Functions secrets and rotation.