Migrate from Firebase Dynamic Links
Why now
Google shut down Firebase Dynamic Links on August 25, 2025. Every *.page.link link stopped resolving that day, the Dynamic Links SDKs no longer deliver deferred deep links, and the console no longer creates links. An app that still calls getDynamicLink() on first launch gets nothing back; a link printed on packaging or sitting in an old email goes nowhere.
LinkForty covers the same ground — links that open the app or fall through to the store or the web, deferred deep linking on first launch, and attribution of the install to the link — on your own host: a custom domain, or the LinkForty domain every workspace gets.
What maps to what
| Firebase Dynamic Links | LinkForty | Read |
|---|---|---|
getDynamicLink() / getInitialLink() on first launch | the deferred deep link callback | Deferred deep linking |
onLink / handleUniversalLink / dynamicLink(fromCustomSchemeURL:) | the direct deep link handler | your platform's SDK page |
*.page.link domain | your workspace's link host or a custom domain | Custom domains |
DynamicLinkParameters with iosParameters / androidParameters / navigationInfoParameters | fields on a link and its template: store URLs, web fallback, deep-link path | Create a link · Link templates |
socialMetaTagParameters | the link's social preview | Social previews |
googleAnalyticsParameters (utm_source …) | UTM parameters on the link | UTM parameters |
buildShortLink() from the app | createLink on the SDK, or the REST API | Create a link |
the link URL inside a Dynamic Link | the link's deep-link path and custom parameters, delivered as deepLinkPath / customParameters | Deferred deep linking |
The association files Firebase hosted for you (apple-app-site-association, assetlinks.json) are served by LinkForty on your link host from your App Configuration.
Order of operations
The order keeps your users working while you switch.
- Create a workspace and pick your host. Every workspace gets
something.toapp.me; add a custom domain if you want your own. Enter your app's Team ID, bundle ID, package name and signing fingerprint under Workspace Settings → App Configuration so the association files are served. - Recreate the links you still need. Firebase's links are gone with the service, so there is nothing to import: create each one in the dashboard, through the API, or in bulk as a CSV under Dashboard → Migration (download the template, preview to check short-code conflicts, then import).
- Install the SDK and replace the listeners. Follow your platform's page — Expo, React Native, iOS, Android, Flutter — or use the prompt below. Route the LinkForty callbacks to the same screens the Dynamic Links handlers reached. Keep the old
page.linkentitlements and intent filters in place for now. - Verify. A fresh install from a LinkForty link lands on the right screen and shows under Installs; a link tapped with the app installed opens it directly; SDK Doctor reports the SDK installed.
- Remove the Dynamic Links SDK last — the dependency, its initialization, the
page.linkentitlements and intent filters — in its own commit, once step 4 holds.
Do the code side with AI
This prompt walks a coding agent through steps 3–5 in your project: it inventories every Dynamic Links call before editing anything, asks you which screen each link should open, installs the LinkForty SDK for your platform, replaces each call using the table above, verifies, and only then removes the old SDK.
Show the prompt
I use LinkForty (https://linkforty.com), a deep-linking and mobile attribution platform: short links that route each click to the app, the app store or the web, and record clicks, installs and in-app events per link.
Before writing any code, read the LinkForty documentation index at https://docs.linkforty.com/llms-full.txt and the pages listed under "Docs" below. Work from what the docs say, not from assumptions about how similar products work. If something you need is not in the docs or in this prompt — an ID, a token, a URL — stop and ask me for it rather than inventing a placeholder that looks real.
## Task: replace Firebase Dynamic Links with LinkForty in this app
Firebase Dynamic Links was shut down on 08-25-2025: the `*.page.link` links no longer resolve and the SDK's deferred deep links no longer arrive. Move this app to LinkForty so links open the app again, an install that follows a click is attributed, and the deep-link path reaches the right screen on first launch — without breaking anything that works today.
## Values
- **App token**: shown in the LinkForty dashboard under Workspace Settings → App Token (it starts with `at_`). Ask me for it; put it in the app's config or environment, not hard-coded in a committed source file.
- **Base URL**: `https://api.linkforty.com` for LinkForty Cloud. (Only a self-hosted LinkForty server uses a different origin — ask if you are not sure which this is.)
- **Link host**: the host my links are on, shown in the dashboard under Settings → Domains — the workspace's own host (it looks like `something.toapp.me`) and any verified custom domain listed there. Ask me for it. This is the domain the app must claim for Universal Links / App Links.
- **LinkForty MCP server** (optional): if it is configured in this environment (https://mcp.linkforty.com/mcp, or the `linkforty-mcp` command), use `get_app_config` to read the workspace's app settings and `create_link` to make a test link at the end. Do not set it up for me if it is not.
- **Old domains → new**: Every `*.page.link` host (and any custom Dynamic Links domain) becomes a LinkForty host: the workspace's own link host, or a verified custom domain (https://docs.linkforty.com/guides/links/custom-domains). Firebase Dynamic Links was shut down on 08-25-2025, so the old links no longer resolve; there is nothing to keep serving, but the old entitlements and intent filters stay until the new links are live (step 6).
## How to work
1. **Look before you change anything.** Identify the framework, its version, the navigation/router library and where the app initializes at launch. List the screens (routes) you found, and **ask me which screen each incoming link should open and what parameter it needs** — for example "a link with `productId` opens the product screen". Wait for my answer before editing; do not guess routes.
2. **Work on a new git branch.** If the working tree has uncommitted changes, stop and tell me instead of building on top of them.
3. **Follow the docs pages named below, not memory.** Package names, method names and config keys come from those pages; LinkForty's SDKs are not the same as other attribution SDKs you may know.
4. **Do not invent values.** Anything in "Values" that says "ask me" is something you ask for. Never commit a token to source.
5. **Keep the app working without a link.** A launch with no matching click must behave exactly as before: no crash, no error surfaced to the user, no blocked start-up.
6. **Finish with a summary**: every file you changed and why; the manual steps that remain for me (Apple Team ID and bundle ID, Play signing SHA-256 fingerprint, store listing, a rebuild and reinstall) and where in the LinkForty dashboard each one goes (Workspace Settings → App Configuration); and anything you were unsure about.
7. **Verify.** Run the app once, then check the LinkForty dashboard: the SDK Doctor badge in the sidebar (https://docs.linkforty.com/guides/attribution/sdk-health) should report the SDK as installed. If the MCP server is available, create a test link with `create_link` and open it on a device or simulator; the click and the deep link should reach the screen we agreed on.
## Migration steps
Do these in this order. The order is what keeps existing users working while the switch happens.
1. **Inventory the old SDK before changing anything.** Find every use of Firebase Dynamic Links: `firebase_dynamic_links` / `FirebaseDynamicLinks` (iOS), `com.google.firebase:firebase-dynamic-links` (Android), `@react-native-firebase/dynamic-links` (React Native), `expo-firebase-dynamic-links` or a config plugin (Expo), the `firebase_dynamic_links` pub package (Flutter); `*.page.link` anywhere in code, entitlements, manifests or config; `getDynamicLink`, `onLink`, `DynamicLinkComponents`, `DynamicLinkParameters`, `buildShortLink`. Also the platform wiring: associated-domain entitlements, `AndroidManifest.xml` intent filters, Expo config plugins, Info.plist keys and any server-side code that builds links. List each with file and line, grouped as *initialization*, *incoming-link handling*, *link creation*, *events*, *platform config*. Do not edit yet.
2. **Agree the mapping.** For each old domain, which LinkForty host replaces it; for each incoming-link handler, which screen it should open and with what parameter (this is the ask-which-screen step from "How to work"); which events matter. Wait for my answers.
3. **Install LinkForty** by following the platform section below that matches this app — only that one. The link host you claim is the LinkForty host from step 2; keep the old domains' entitlements and intent filters in place alongside it for now.
4. **Replace each inventoried call** using the concept map. Route the LinkForty callbacks to the same screens the old handlers reached, so a link that worked before still works after. Where the old SDK created links from inside the app, either wire `createLink` (needs an API key — ask me) or tell me those links should be created in the dashboard instead.
5. **Verify LinkForty end to end** — a fresh install from a LinkForty link lands on the agreed screen and shows under Installs; a link tapped with the app installed opens it directly; an event arrives; SDK Doctor reports the SDK installed. Do not go past this step until it holds.
6. **Only now remove Firebase Dynamic Links**: the dependency, its initialization, its keys and config plugins, the old domains' entitlements and intent filters, and any dead handlers — in a separate commit, so it can be reverted on its own if anything surfaces.
7. **The links themselves.** Firebase's links are gone with the service, so each one you still need is recreated in LinkForty: through the dashboard, through the REST API (https://docs.linkforty.com/api/endpoints/links/create-link), or in bulk as a CSV through Dashboard → Migration, which offers a downloadable template and a preview step that checks short-code conflicts before writing anything (https://docs.linkforty.com/migrations/core-to-cloud#step-3--import-into-cloud shows the importer). List the Dynamic Links you find in the codebase, marketing config or docs with what each pointed to, so I can recreate them; do not create them yourself unless the MCP server is available and I say so.
## Concept map: Firebase Dynamic Links → LinkForty
| Firebase Dynamic Links | LinkForty |
|---|---|
| `getDynamicLink()` / `getInitialLink()` on first launch (deferred) | the deferred deep link callback — https://docs.linkforty.com/guides/attribution/deferred-deep-linking |
| `onLink` / `handleUniversalLink` / `dynamicLink(fromCustomSchemeURL:)` | the direct deep link handler on the SDK page for this platform |
| `*.page.link` domain | the workspace's link host or a custom domain — https://docs.linkforty.com/guides/links/custom-domains |
| `DynamicLinkParameters` with `iosParameters` / `androidParameters` / `navigationInfoParameters` (store IDs, fallback URLs, minimum versions) | fields on a LinkForty link and its template: store URLs, web fallback, deep-link path — https://docs.linkforty.com/api/endpoints/links/create-link · https://docs.linkforty.com/guides/links/link-templates |
| `socialMetaTagParameters` (title, description, image) | the link's social preview — https://docs.linkforty.com/guides/links/social-previews |
| `googleAnalyticsParameters` (`utm_source` …) | UTM parameters on the link — https://docs.linkforty.com/guides/links/utm-parameters |
| `buildShortLink()` from the app | `createLink` on the SDK (needs an API key) or the REST API — https://docs.linkforty.com/api/endpoints/links/create-link |
| the `link` deep-link URL inside a Dynamic Link | the link's deep-link path plus custom parameters, delivered to the app in the callback's `deepLinkPath` / `customParameters` |
## Install LinkForty — follow the one section for this app's platform
### If this app is Expo
**Steps**
1. **Install** with `npx expo install @linkforty/mobile-sdk-expo expo-device expo-application expo-localization expo-linking @react-native-async-storage/async-storage` (https://docs.linkforty.com/sdks/expo#installation). Note: deep links do not work in Expo Go; a development build is needed to test them.
2. **Initialize once at launch** in the root layout or App component: `await LinkForty.initialize({ baseUrl, appToken, debug: __DEV__ })` from `@linkforty/mobile-sdk-expo`, with the base URL and app token from "Values" (https://docs.linkforty.com/sdks/expo#initialize-the-sdk). Leave `apiKey` out — it is only for creating links from the app.
3. **Claim the link host** so the OS opens the app instead of the browser (https://docs.linkforty.com/sdks/expo#platform-configuration): add `applinks:<link host>` to `ios.associatedDomains` and an `android.intentFilters` entry with `autoVerify: true` for `https://<link host>` in `app.json` / `app.config.*`, then regenerate the native projects if this app prebuilds. LinkForty serves the AASA and assetlinks.json files on that host itself; nothing to upload. Tell me the Apple Team ID / bundle ID and Android package / signing SHA-256 must be entered in LinkForty under Workspace Settings → App Configuration, or the OS will not verify the domain.
4. **Deferred deep link**: register `LinkForty.onDeferredDeepLink((data) => …)` right after initialize. When `data` is non-null, route to the screen we agreed on using its `deepLinkPath` / `customParameters`; when it is null (organic install) do nothing (https://docs.linkforty.com/sdks/expo#deep-linking).
5. **Direct deep links**: register `LinkForty.onDeepLink((url, data) => …)` and route the same way. Use the app's existing Expo Router / React Navigation navigation for both — do not add a second navigation system.
6. **One in-app event**: at the obvious success moment in this app (sign-up completed, purchase, or the first key action — tell me which you chose) call `LinkForty.trackEvent('signup')` or the purchase equivalent with amount and ISO currency (https://docs.linkforty.com/sdks/expo#track-events). No personal data in properties.
**Docs for this platform**
- Expo SDK: https://docs.linkforty.com/sdks/expo (Installation, Platform Configuration, Initialize the SDK, Deep Linking, Track Events)
- SDK integration overview: https://docs.linkforty.com/guides/attribution/sdk-integration
- Deferred deep linking: https://docs.linkforty.com/guides/attribution/deferred-deep-linking
- Link hosts and app association files: https://docs.linkforty.com/guides/links/custom-domains#your-linkforty-domain
- SDK Doctor: https://docs.linkforty.com/guides/attribution/sdk-health
### If this app is React Native
**Steps**
1. **Install** with `npm install @linkforty/mobile-sdk-react-native`, then `cd ios && pod install` (https://docs.linkforty.com/sdks/react-native#installation). Android needs no extra setup (auto-linking).
2. **Initialize once at launch**, before registering any callback: `LinkForty.init({ baseUrl, appToken, debug: __DEV__ })` from `@linkforty/mobile-sdk-react-native`, with the base URL and app token from "Values" (https://docs.linkforty.com/sdks/react-native#initialize-the-sdk). Leave `apiKey` out — it is only for creating links from the app.
3. **Claim the link host** (https://docs.linkforty.com/sdks/react-native#platform-configuration): iOS — add the Associated Domains capability with `applinks:<link host>`; Android — add an `<intent-filter android:autoVerify="true">` for `https://<link host>` to the main activity in `AndroidManifest.xml`, with `android:launchMode="singleTask"`. LinkForty serves the AASA and assetlinks.json files on that host itself; nothing to upload. Tell me the Apple Team ID / bundle ID and Android package / signing SHA-256 must be entered in LinkForty under Workspace Settings → App Configuration, or the OS will not verify the domain.
4. **Deferred deep link**: register `LinkForty.onDeferredDeepLink((data) => …)` right after init. When `data` is non-null, route to the screen we agreed on using its `deepLinkPath` / `customParameters`; when it is null (organic install) do nothing.
5. **Direct deep links**: register `LinkForty.onDeepLink((url, data) => …)` and route the same way, through the app's existing React Navigation setup — do not add a second navigation system.
6. **One in-app event**: at the obvious success moment in this app (sign-up completed, purchase, or the first key action — tell me which you chose) call `LinkForty.trackEvent('signup')`, or `LinkForty.trackRevenue(amount, 'USD')` for a purchase (https://docs.linkforty.com/sdks/react-native#track-events). No personal data in properties.
**Docs for this platform**
- React Native SDK: https://docs.linkforty.com/sdks/react-native (Installation, Platform Configuration, Initialize the SDK, Track Events)
- SDK integration overview: https://docs.linkforty.com/guides/attribution/sdk-integration
- Deferred deep linking: https://docs.linkforty.com/guides/attribution/deferred-deep-linking
- Link hosts and app association files: https://docs.linkforty.com/guides/links/custom-domains#your-linkforty-domain
- SDK Doctor: https://docs.linkforty.com/guides/attribution/sdk-health
### If this app is Native iOS (Swift)
**Steps**
1. **Install** the package `https://github.com/LinkForty/mobile-sdk-ios.git` with Swift Package Manager (or CocoaPods / Carthage if this project already uses one) and `import LinkFortySDK` (https://docs.linkforty.com/sdks/ios#installation).
2. **Initialize once at launch** — in the `@main` App struct's `init` or `AppDelegate`: build a `LinkFortyConfig(baseURL:appToken:debug:)` with the base URL and app token from "Values" and call `try await LinkForty.shared.initialize(config:)` (https://docs.linkforty.com/sdks/ios#initialize-the-sdk). Leave `apiKey` out — it is only for creating links from the app. Initialization failure must be logged, never fatal.
3. **Claim the link host** (https://docs.linkforty.com/sdks/ios#universal-links-setup): add the Associated Domains capability with `applinks:<link host>` (one entry per host if there is also a custom domain). LinkForty serves the apple-app-site-association file on that host itself; nothing to upload. Tell me the Apple Team ID and bundle ID must be entered in LinkForty under Workspace Settings → App Configuration, or Universal Links will not verify.
4. **Deferred deep link**: register `LinkForty.shared.onDeferredDeepLink { data in … }` right after initialize. When `data` is non-nil, route to the screen we agreed on using its `deepLinkPath` / `customParameters`; when it is nil (organic install) do nothing (https://docs.linkforty.com/sdks/ios#handle-deferred-deep-links).
5. **Direct deep links** (https://docs.linkforty.com/sdks/ios#handle-direct-deep-links): pass incoming URLs to `LinkForty.shared.handleDeepLink(url:)` — from SwiftUI `.onOpenURL`; in a UIKit app from `application(_:open:options:)` and, for Universal Links, the `continue userActivity` delegate method's `webpageURL` — and register `LinkForty.shared.onDeepLink { url, data in … }` to route the same way through the app's existing navigation.
6. **One in-app event**: at the obvious success moment in this app (sign-up completed, purchase, or the first key action — tell me which you chose) call `trackEvent` with `signup`, or `trackRevenue` with amount and ISO currency for a purchase (https://docs.linkforty.com/sdks/ios#track-events). No personal data in properties.
**Docs for this platform**
- iOS SDK: https://docs.linkforty.com/sdks/ios (Installation, Initialize the SDK, Handle Deferred Deep Links, Handle Direct Deep Links, Universal Links Setup, Track Events)
- SDK integration overview: https://docs.linkforty.com/guides/attribution/sdk-integration
- Deferred deep linking: https://docs.linkforty.com/guides/attribution/deferred-deep-linking
- Link hosts and app association files: https://docs.linkforty.com/guides/links/custom-domains#your-linkforty-domain
- SDK Doctor: https://docs.linkforty.com/guides/attribution/sdk-health
### If this app is Native Android (Kotlin)
**Steps**
1. **Install** by adding `implementation("com.linkforty:sdk:<version on the docs page>")` to the app module's Gradle dependencies (https://docs.linkforty.com/sdks/android#installation).
2. **Initialize once** in the `Application` subclass's `onCreate` (create one if the app has none, and register it in the manifest): build a `LinkFortyConfig(baseURL = …, appToken = …, debug = BuildConfig.DEBUG)` with the base URL and app token from "Values" and call `LinkForty.initialize(this, config)` (https://docs.linkforty.com/sdks/android#initialize-the-sdk). Leave `apiKey` out — it is only for creating links from the app.
3. **Claim the link host** (https://docs.linkforty.com/sdks/android#app-links-setup): add an `<intent-filter android:autoVerify="true">` with `<data android:scheme="https" android:host="<link host>" />` to the main activity, one `<data>` element per host if there is also a custom domain, and set `android:launchMode="singleTask"` on that activity. LinkForty serves assetlinks.json on that host itself; nothing to upload. Tell me the package name and the signing certificate SHA-256 fingerprint (release and debug) must be entered in LinkForty under Workspace Settings → App Configuration, or the OS will not verify the domain.
4. **Deferred deep link**: register `LinkForty.shared.onDeferredDeepLink { data -> … }` right after initialize. When `data` is non-null, route to the screen we agreed on using its `deepLinkPath` / `customParameters`; when it is null (organic install) do nothing (https://docs.linkforty.com/sdks/android#handle-deferred-deep-links).
5. **Direct deep links** (https://docs.linkforty.com/sdks/android#handle-direct-deep-links): in the main activity pass `intent.data` to `LinkForty.shared.handleDeepLink(uri)` from both `onCreate` **and** `onNewIntent` — without `onNewIntent` a link tapped while the app is in the background is lost (https://docs.linkforty.com/sdks/android#important-onnewintent) — and register `LinkForty.shared.onDeepLink { url, data -> … }` to route the same way through the app's existing navigation.
6. **One in-app event**: at the obvious success moment in this app (sign-up completed, purchase, or the first key action — tell me which you chose) call `trackEvent` with `signup`, or `trackRevenue` with amount and ISO currency for a purchase (https://docs.linkforty.com/sdks/android#track-events). No personal data in properties. If the app uses ProGuard / R8, apply the keep rules on the docs page.
**Docs for this platform**
- Android SDK: https://docs.linkforty.com/sdks/android (Installation, Initialize the SDK, Handle Deferred Deep Links, Handle Direct Deep Links, App Links Setup, Track Events)
- SDK integration overview: https://docs.linkforty.com/guides/attribution/sdk-integration
- Deferred deep linking: https://docs.linkforty.com/guides/attribution/deferred-deep-linking
- Link hosts and app association files: https://docs.linkforty.com/guides/links/custom-domains#your-linkforty-domain
- SDK Doctor: https://docs.linkforty.com/guides/attribution/sdk-health
### If this app is Flutter
**Steps**
1. **Install** with `flutter pub add linkforty_flutter` (https://docs.linkforty.com/sdks/flutter#installation).
2. **Initialize once** in `main()` after `WidgetsFlutterBinding.ensureInitialized()`: build a `LinkFortyConfig(baseURL: Uri.parse(…), appToken: …, debug: kDebugMode)` with the base URL and app token from "Values" and `await LinkForty.initialize(config: config)` inside a try/catch — a failure is logged, never fatal, and `runApp` still runs (https://docs.linkforty.com/sdks/flutter#initialize-the-sdk). Leave `apiKey` out — it is only for creating links from the app.
3. **Claim the link host** (https://docs.linkforty.com/sdks/flutter#platform-setup): Android — an `<intent-filter android:autoVerify="true">` for `https://<link host>` on the main activity in `AndroidManifest.xml`; iOS — the Associated Domains capability with `applinks:<link host>` in the Runner target. One entry per host if there is also a custom domain. LinkForty serves the AASA and assetlinks.json files on that host itself; nothing to upload. Tell me the Apple Team ID / bundle ID and Android package / signing SHA-256 must be entered in LinkForty under Workspace Settings → App Configuration, or the OS will not verify the domain.
4. **Deferred deep link**: register `LinkForty.instance.onDeferredDeepLink((data) { … })` right after initialize. When `data` is non-null, route to the screen we agreed on using its `deepLinkPath` / `customParameters` through the app's existing navigator (a `navigatorKey`, go_router, or whatever it uses); when it is null (organic install) do nothing (https://docs.linkforty.com/sdks/flutter#handle-deferred-deep-links).
5. **Direct deep links**: register `LinkForty.instance.onDeepLink((uri, data) { … })` and route the same way (https://docs.linkforty.com/sdks/flutter#handle-direct-deep-links). The SDK handles the incoming-link plumbing itself; do not add a second app_links / uni_links listener for the link host.
6. **One in-app event**: at the obvious success moment in this app (sign-up completed, purchase, or the first key action — tell me which you chose) call `trackEvent` with `signup`, or `trackRevenue` with amount and ISO currency for a purchase (https://docs.linkforty.com/sdks/flutter#track-events). No personal data in properties.
**Docs for this platform**
- Flutter SDK: https://docs.linkforty.com/sdks/flutter (Installation, Initialize the SDK, Handle Deferred Deep Links, Handle Direct Deep Links, Platform Setup, Track Events)
- SDK integration overview: https://docs.linkforty.com/guides/attribution/sdk-integration
- Deferred deep linking: https://docs.linkforty.com/guides/attribution/deferred-deep-linking
- Link hosts and app association files: https://docs.linkforty.com/guides/links/custom-domains#your-linkforty-domain
- SDK Doctor: https://docs.linkforty.com/guides/attribution/sdk-health
## Done when
- Every Firebase Dynamic Links call in the inventory has a LinkForty replacement that reaches the same screen, and the app has no remaining reference to the old SDK, its keys or its domains.
- A fresh install from a LinkForty link lands on the agreed screen and is attributed; a tap with the app installed opens it directly; events arrive; SDK Doctor reports the SDK installed.
- The removal of Firebase Dynamic Links is its own commit, made after the verification above.
- I have the list of old links to recreate or confirm after import.
When you are done, tell me: exactly which files changed and why, how I can verify it worked end to end (what to tap or run, and what I should see in the LinkForty dashboard), and anything you were unsure about.What the prompt will ask you
- Which old domain maps to which new host — each
*.page.linkdomain you used, and the LinkForty host (your label or custom domain) that replaces it. - Which screen each incoming path should open, and what parameter it needs — the agent lists the screens it finds and waits for your answer rather than guessing.
- Your app token (Workspace Settings → App Token), and an API key only if the app created links at runtime.
- The links it finds in your code or config, so you can recreate them in step 2.