
Migrating from Firebase Dynamic Links: a practical guide
This post was first published on Medium.
Firebase Dynamic Links is approaching its end-of-life on August 25th, 2025. This post will guide you through a pragmatic migration approach that works for both React Native and native apps.
Scope and limitations
Product needs vs marketing needs
Firebase Dynamic Links migrated using this approach will satisfy most product needs for deep linking within your application. The solution provides a reliable way to handle all standard deep linking scenarios such as sharing content, referrals, and navigating users to specific screens across mobile and web.
For links that require advanced ad tracking or attribution capabilities, this migration solution works best when complemented with full-fledged attribution platforms like Adjust or AppsFlyer. This creates a beneficial separation of concerns:
- Product links remain under your complete control through your own infrastructure
- Marketing links can be managed by specialized third-party services
This dual approach prevents vendor lock-in for your core product functionality. Since marketing campaigns are typically short-lived operations, using third-party links for these purposes poses minimal long-term risk even if those services change or deprecate in the future.
Side benefits of the migration
Moving away from Firebase Dynamic Links isn’t just about avoiding a service shutdown. You’ll also get:
- Full control over your linking infrastructure
- Better security options like authenticated links if you implement them
- Custom analytics tailored to your needs
- Simpler privacy compliance by controlling how user data is handled
The deferred linking limitation
It’s important to note that this migration approach has one significant limitation: it won’t support deferred dynamic links, although you may try to implement this yourself. Deferred deep linking refers to the process where:
- A user clicks on a link without having your app installed
- They are redirected to the corresponding app store
- The actual deferred deep linking feature: after installing the app, they are automatically redirected to the specific deep link content
This “install-and-redirect” flow relies on specific proprietary mechanisms in Firebase Dynamic Links that cannot be easily replicated in a custom solution without significant complexity.
If deferred linking is crucial for your app, you might need to:
- Look into commercial alternatives
- Add a manual step in the onboarding asking users if they came from a specific link
- Implement custom tracking cookies or browser fingerprinting techniques (subject to platform limitations and privacy considerations)
For most apps though, standard deep linking will cover the core functionality needed.
The migration plan
Step 1: Create a proxy backend endpoint and collect links
First, you’ll create a backend endpoint that intercepts link creation requests, forwards them to Firebase, and stores them in your database. This should be done quickly, ideally before the end of May 2025, to capture as many links as possible.
With React Native, you can push this change via AppZung CodePush since it doesn’t require native code modifications.
Backend side
Create a create-dynamic-link
endpoint that:
- Validates input data (check if links are valid/safe with a domain allowlist)
- Checks for duplicate links already in the database
- Generates a random suffix if needed
- Creates the link on Firebase using their API (while the service is still active)
- Saves the dynamic link to your database
- Handles errors properly
Your input/output might look something like:
input AndroidCreateDynamicLinkInput {
minimumPackageVersionCode: Int
}
input IOSCreateDynamicLinkInput {
minimumVersion: String
}
input OtherPlatformCreateDynamicLinkInput {
fallbackUrl: String
}
input CreateDynamicLinkInput {
android: AndroidCreateDynamicLinkInput
deeplink: String!
ios: IOSCreateDynamicLinkInput
otherPlatform: OtherPlatformCreateDynamicLinkInput
social: SocialCreateDynamicLinkInput
}
input SocialCreateDynamicLinkInput {
description: String
imageUrl: String
title: String
}
type DynamicLink {
androidMinimumPackageVersionCode: Int
deeplink: String!
id: ID!
iosMinimumVersion: String
link: String!
}
type CreateDynamicLinkOutput {
dynamicLink: DynamicLink!
}
Client side
When creating new links, instead of calling Firebase’s SDK (e.g., dynamicLinks().buildShortLink
), call your new endpoint.
You may also add a simple (or more sophisticated) client cache in order to prevent multiple calls for the same dynamic link parameters.
A simple example in Typescript would be:
const generatedDynamicLinksDuringSessionCache = new Map<string, string>();
export const generateShortDynamicLink = async (
{ deeplinkPath, social, webUrl }: FirebaseDynamicLinkParams
) => {
const cacheKey = JSON.stringify({ deeplinkPath, social, webUrl });
const alreadyGeneratedDynamicLinkDuringSession =
generatedDynamicLinksDuringSessionCache.get(cacheKey);
if (alreadyGeneratedDynamicLinkDuringSession) {
return alreadyGeneratedDynamicLinkDuringSession;
}
const { link } = await YourApi.createDynamicLink({
deeplink: `https://${process.env.DYNAMIC_LINK_MAIN_CUSTOM_DOMAIN}/${deeplinkPath}`,
otherPlatform: {
fallbackUrl: webUrl,
},
social,
});
generatedDynamicLinksDuringSessionCache.set(cacheKey, link);
return link;
};
Import existing links
You’ll need to export your existing links from Firebase using Google Takeout:
- Go to Google Takeout
- Select only Firebase project data
- Follow the export process
Then import them into your database. Your import script should:
- Validate the data
- Filter out archived links
- Format social descriptions to fit your database constraints
- Avoid duplicates
Repeat this export/import monthly until Firebase’s sunset date to catch links generated by older app versions.
Step 2: Replace Firebase Dynamic Links in your app
Once most of your users have the first step modifications (if you use AppZung CodePush it should be quick!), it’s time to completely remove the Firebase Dynamic Links SDK and replace it with your implementation.
This step should be done ideally before the end of June 2025 so that most of your user base has the updated native code when Firebase Dynamic Links is stopped.
Backend side
Create a consume-dynamic-link
endpoint that:
- Retrieves link data by suffix
- Optionally tracks usage
- Handles errors for invalid suffixes
input ConsumeDynamicLinkInput {
suffix: String!
}
type DynamicLink {
androidMinimumPackageVersionCode: Int
deeplink: String!
id: ID!
iosMinimumVersion: String
link: String!
}
type ConsumeDynamicLinkOutput {
dynamicLink: DynamicLink!
}
Client side
These changes will ensure that when a user who already has the app clicks on a dynamic link, it opens the link gracefully.
- Make sure Android’s intent filter is auto-verified:
<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:host="@string/firebase_dynamic_link_main_custom_domain" android:scheme="https"/>
</intent-filter>
- Ensure your iOS
AppDelegate
handles universal links by implementingcontinueUserActivity
:
- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
// example for React Native
return [RCTLinkingManager application:application continueUserActivity:userActivity restorationHandler:restorationHandler];
}
-
Remove Firebase Dynamic Links SDK
-
Where you used to listen for the Firebase Dynamic Links, adapt it so that it listens for deeplinks and check if this is a dynamic link. If this is the case, parse it, eventually call your backend if it’s a short link, and use it.
For React Native:
- Remove calling
@react-native-firebase/dynamic-links
in your linking config’sgetInitialURL
/subscribe
- Instead, check if the deeplink returned by React Native uses the dynamic link domain
- If so, call the below
getDynamicLinkFromURL
and handle the dynamic link like before
import queryString from 'query-string';
import { Platform } from 'react-native';
import { firebaseDynamicLinkMainCustomDomainPrefix } from './linkingPrefixes';
const dynamicLinkCacheDuringSession = new Map<string, DynamicLink>();
export const getDynamicLinkFromUrl = async (url: string): Promise<DynamicLink | undefined> => {
if (!url.startsWith(firebaseDynamicLinkMainCustomDomainPrefix)) {
return;
}
const alreadyKnownDynamicLinkDuringSession = dynamicLinkCacheDuringSession.get(url);
if (alreadyKnownDynamicLinkDuringSession) {
return alreadyKnownDynamicLinkDuringSession;
}
const { query, url: parsedUrl } = queryString.parseUrl(url);
if (query.link) {
// handle long dynamic links
const firebaseDynamicLink = {
url: query.link,
minimumAppVersion:
(Platform.OS === 'android'
? query.amv != null
? parseInt(query.amv, 10)
: null
: query.imv) || null,
utmParameters: {}, // FUTURE implement this if needed
};
dynamicLinkCacheDuringSession.set(url, firebaseDynamicLink);
return firebaseDynamicLink;
}
const suffix = parsedUrl.replace(firebaseDynamicLinkMainCustomDomainPrefix + '/', '');
const isAlreadyADeeplink = suffix.includes('/');
if (isAlreadyADeeplink) {
return {
url,
minimumAppVersion: null,
utmParameters: {}, // FUTURE implement this if needed
};
}
const response = await Api.consumeDynamicLink({
suffix,
platform: Platform.OS, // optional, for analytics
})
.catch((error) => {
handleError(error);
return null;
});
const dynamicLink = response?.dynamicLink;
if (!dynamicLink) {
return;
}
const firebaseDynamicLink = {
url: dynamicLink.deeplink,
minimumAppVersion:
(Platform.OS === 'android'
? dynamicLink.androidMinimumPackageVersionCode
: dynamicLink.iosMinimumVersion) || null,
utmParameters: {}, // FUTURE implement this if needed
};
dynamicLinkCacheDuringSession.set(url, firebaseDynamicLink);
return firebaseDynamicLink;
};
Step 3: Set up web redirects
For users without the app installed or those on desktop, you’ll need web redirects.
This step is less urgent and can be done much closer to the sunset date.
The basic idea is to use your Firebase Dynamic Links domain as a website or server. You can use a simple static website or serverless functions to detect the platform, call your consume-dynamic-link endpoint, and redirect users accordingly. Using a simple static website will be free, but it has some limitations: no dynamic social meta tags, and the code to detect the platform might take a while to load.
I haven’t actually implemented this step for my clients yet as it’s not an immediate priority, but I’ll update this article once I have a concrete implementation to share.
AppCenter CodePush is also going away
Since we’re talking about service migrations, it’s worth mentioning that Microsoft’s AppCenter CodePush is being retired in March 2025. If you’re a React Native developer dealing with both migrations, AppZung CodePush offers a drop-in replacement with:
- One-command migration (until March 31st as AppCenter will be down afterward)
- Feature parity
- EU hosting with worldwide CDN
- New privacy, security and delivery features
- No vendor lock-in as our maintained RN module is compatible with the open-source code-push-server
- Happy customers!
I personally don’t believe it’s necessary, but if there is enough user demand, we might add Dynamic Links to AppZung, so contact us if this is the case :)
Conclusion
Migrating from Firebase Dynamic Links might seem like a pain, but with this step-by-step approach, you can ensure a smooth transition. By taking control of your deep linking infrastructure now, you’ll be prepared for the actual shutdown and gain more flexibility in your implementation.
If you need help migrating, give me a ping at lagrange.louis+consulting@gmail.com.