guides

Firebase Dynamic Links migration: the engineering playbook

the linkboo team·7 min read·updated Mon Jun 01 2026 17:00:00 GMT-0700 (Pacific Daylight Time)
On this page

scope

Step-by-step migration from Firebase Dynamic Links to a working replacement. Assumes you have read /guides/firebase-dynamic-links-replacement and chosen one of the four target architectures. This guide is the execution playbook.

The general post-FDL strategic context lives at the hub: /guides/firebase-dynamic-links-replacement. The consumer-facing manifestation of similar link-failure problems lives at the thesis: /guides/in-app-browser-logged-out.

prerequisites

Before starting the migration:

  • A domain you control with DNS access and the ability to serve static files over HTTPS.
  • App Store / Play Store identifiers (Team ID + bundle ID for iOS, package name + SHA-256 cert fingerprint for Android).
  • Read/write access to your AppDelegate.swift and Application.kt (or equivalent).
  • A staging environment with a TestFlight build and an internal-track Play release.
  • A test plan covering: cold install, warm relaunch, link tap from email, link tap from social-app webview, link tap from notification.

phase 1 — inventory your FDL usage

Audit existing code before changing anything.

# iOS
grep -r "DynamicLinks" ios/ --include="*.swift" --include="*.m"
grep -r "FirebaseDynamicLinks" ios/Podfile
grep -r "page.link" ios/

# Android
grep -r "FirebaseDynamicLinks" android/ --include="*.kt" --include="*.java"
grep -r "firebase-dynamic-links" android/app/build.gradle
grep -r "page.link" android/

# Server-side / web
grep -r "page.link" web/ src/

Classify each callsite:

Pattern What it indicates
DynamicLinks.dynamicLinks().handleUniversalLink(...) iOS universal-link handling — replace with native application(_:continue:)
getDynamicLink(intent) (Android) App-launch handling — replace with native Intent.getData()
pendingDynamicLink first-launch read Deferred deep linking — needs an attribution replacement
DynamicLinkComponents builder Server-side or client-side link generation — replace with your own URL builder
Any URL of form yourapp.page.link/* in stored content Inbound traffic to redirect post-shutdown

The deferred-deep-linking callsites are the hard ones. Mechanism: /guides/deferred-deep-linking-explained.

Replace FDL's hosted association files with your own. Detailed format documents: /guides/aasa-file-explained and /guides/assetlinks-json-explained. Setup walkthroughs: /guides/ios-universal-links-setup and /guides/android-app-links-setup.

The shape:

iOS — https://yourdomain.com/.well-known/apple-app-site-association

{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCD1234EF.com.yourcompany.yourapp"],
        "components": [
          {
            "/": "/l/*",
            "comment": "Matches short-link routes"
          },
          {
            "/": "/share/*",
            "comment": "Matches share-card routes"
          }
        ]
      }
    ]
  }
}

Served with Content-Type: application/json, no extension, no redirects, on the apex or www per your associated-domains entitlement.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.yourcompany.yourapp",
    "sha256_cert_fingerprints": [
      "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
    ]
  }
}]

Verify before proceeding:

# AASA
curl -sI https://yourdomain.com/.well-known/apple-app-site-association | grep -i content-type
# Expected: application/json; charset=utf-8

# assetlinks.json
curl -s https://digitalassetlinks.googleapis.com/v1/statements:list \
  ?source.web.site=https://yourdomain.com \
  &relation=delegate_permission/common.handle_all_urls

Apple caches AASA aggressively. After the first publish, a device may need a full app reinstall to pick up changes. Use the Apple swcutil tool on a development device to verify the file was fetched.

Replace FDL's handler with the platform-native primitives.

iOS — AppDelegate.swift

func application(_ application: UIApplication,
                 continue userActivity: NSUserActivity,
                 restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL,
          let components = URLComponents(url: incomingURL, resolvingAgainstBaseURL: true) else {
        return false
    }

    // route by path
    let path = components.path
    if path.hasPrefix("/l/") {
        return routeShortLink(slug: String(path.dropFirst(3)))
    }
    return false
}

Android — MainActivity.kt

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    val data: Uri? = intent.data
    if (data != null && data.pathSegments.firstOrNull() == "l") {
        val slug = data.pathSegments.getOrNull(1) ?: return
        routeShortLink(slug)
    }
}

For Android, register the intent filter in AndroidManifest.xml:

<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="yourdomain.com" android:pathPrefix="/l/" />
</intent-filter>

The autoVerify="true" flag is what makes this an App Link rather than a plain deep link. The semantic distinction matters: /guides/universal-link-vs-deep-link-vs-app-link.

phase 4 — redirect inbound FDL URLs

Even though FDL no longer resolves, inbound traffic to yourapp.page.link/xyz URLs continues for months — pasted in old emails, screenshots, embedded media. Capture it.

You no longer control the *.page.link domain. Google's hosted DNS for that subdomain is dead. Inbound traffic to it can't be redirected by you.

What you can redirect: any link that was rendered through your own infrastructure that referenced *.page.link. Find and replace them in:

  • Email templates
  • In-app share-sheet output
  • Web property URLs
  • Documentation, support articles, marketing site

For each, point to https://yourdomain.com/l/:slug directly. Slug encoding should match what your new router expects.

If you logged FDL slug → destination mappings in your analytics or your backend, dump that mapping and serve it from your new router so older inbound URLs still resolve to the intended destination.

phase 5 — handle deferred deep linking, or accept the loss

The hardest job to migrate. See /guides/deferred-deep-linking-explained for the full mechanism.

If you're using Branch/AppsFlyer/Adjust: integrate per vendor SDK. Test the cold-install path on a real device — simulators don't reproduce the install-referrer flow.

If you're building your own:

  • Android: register a BroadcastReceiver for INSTALL_REFERRER and parse the referrer string. The Play Store passes it through reliably.
  • iOS: there is no reliable path post-ATT. Best-effort approaches: server-set clipboard handoff before redirect (Apple-restricted), SKAdNetwork conversion postbacks (lossy, aggregated), or a "tap to confirm" first-launch UX that asks the user to re-tap a link from email.

The honest answer: iOS deferred deep linking in 2026 is a degraded product. Plan your install funnel to not depend on it where possible. Pages on adjacent SaaS as references: /guides/branch-io-alternative, /guides/appsflyer-onelink-alternative, /guides/adjust-deep-links-alternative.

phase 6 — remove FDL dependencies

Once the replacement is live and verified in production:

iOS

# Podfile — remove
pod 'Firebase/DynamicLinks'
// AppDelegate.swift — remove
import FirebaseDynamicLinks
FirebaseApp.configure() // keep if other Firebase services used
DynamicLinks.dynamicLinks().handleUniversalLink(...)

Run pod install and verify the framework is no longer linked.

Android

// app/build.gradle — remove
implementation 'com.google.firebase:firebase-dynamic-links-ktx'
// MainActivity.kt — remove
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
FirebaseDynamicLinks.getInstance().getDynamicLink(intent)

Re-run Gradle sync and verify the dependency is gone from app/build/intermediates/.../merged_manifests/.

Firebase console

Remove the Dynamic Links configuration from your Firebase project. The console still shows the FDL section for legacy reasons; it does nothing.

phase 7 — validation

End-to-end smoke tests:

Test Expected behavior
Tap https://yourdomain.com/l/foo in iOS Mail iOS app opens, route to foo destination
Tap same URL in mobile Safari with app installed iOS app opens, route to foo
Tap same URL in mobile Safari with app NOT installed Fallback web page renders
Tap same URL in Instagram in-app browser Universal Link behavior is suppressed in in-app webviews; fallback web page renders. See /guides/in-app-browser-cookies-explained.
Cold install from store, return to original link destination Deferred deep linking — depends on SDK integration
Tap https://yourdomain.com/l/foo on Android Chrome Android app opens, route to foo
Tap on Android with app NOT installed, install from Play Store First launch routes to foo via install-referrer

The in-app-browser failure mode in row 4 is intentional in the platform spec — Universal Links don't trigger inside WKWebView instances. Apps must use universal link fallback handling and the user-side escape is the in-app browser escape flow.

migration timeline

A small app (single product, single platform) can complete the technical work in 3–5 engineering days plus 1 week of staged rollout. A large app with paid-acquisition attribution dependencies should plan 3–6 weeks including marketing-team validation.

Critical path items:

  1. AASA + assetlinks.json publish + DNS/TLS validation: 1–2 days
  2. iOS/Android handler implementation + test: 2–3 days
  3. SDK integration (if SaaS path): 2–5 days
  4. Inbound URL redirect mapping: 1 day
  5. Validation on real devices across iOS/Android matrix: 3–5 days
  6. Production rollout with feature flag: 1 week

If the non-app side of your link infrastructure — bio links, campaign URLs, social-share routing — is part of this migration, the no-SDK linkboo path lives at link.boo/api and link.boo/docs.

references

  • Firebase Dynamic Links FAQ, deprecation timeline October 2023 — August 2025
  • Apple Supporting Universal Links documentation
  • Android App Links Verification documentation
  • Play Install Referrer Library documentation
  • Apple App Tracking Transparency framework documentation

Stop losing the click after the tap.

linkboo escapes the in-app browser so your real page loads — fast.

Start for free →