How to move from Firebase Dynamic Links to Ferry.
Firebase Dynamic Links is gone. Your replacement plan still needs to preserve the important parts: one link across platforms, the right screen after open or install, and attribution you can trust.
AUG
The shutdown date has passed.
Google shut Firebase Dynamic Links down on August 25, 2025. Both custom-domain links
and page.link links stopped being served. Google also says
page.link domains cannot be transferred, and that link metadata was marked
for deletion at shutdown.
That changes the shape of a migration. If you exported your Dynamic Links metadata before shutdown, you can rebuild from that file. If you did not, this is a reconstruction project: recover the destinations and campaign intent from the places where your team used those links, then create durable replacements.
Start with the links that still matter.
Do not begin by recreating every historical short URL. Begin with the links that still have distribution or business value. Old QR codes, packaging, referral programs, lifecycle emails, app invites, and paid campaigns deserve attention before a launch announcement from three years ago.
If you have a Firebase export, normalize it into one working sheet with:
- the old short URL and suffix;
- the deep link or in-app destination;
- iOS, Android, and desktop fallbacks;
- campaign source, medium, and name;
- where the link is still published;
- an owner and migration status.
Without an export, search campaign tools, email templates, QR source files, backend records, app configuration, support macros, and analytics history. Put the recovered links into the same sheet. This inventory becomes the cutover checklist.
Move the links that still have a job, not the links that merely exist.
Map the old model to Ferry.
The concepts are close, but the payload should not be treated as a pile of redirect parameters. In Ferry, a project represents one app. A hostname belongs to one project, and each link carries structured data that your app reads.
| Firebase | Ferry | Migration note |
|---|---|---|
| URL prefix | Shared or custom hostname | Use a new feryl.io hostname, or connect a hostname you own. |
| Deep link parameter | payload.data and optional payload.url | Route from structured data first. Treat the URL as optional. |
| iOS and Android app settings | One Ferry project | Bundle ID, package name, store URLs, and web fallback live together. |
| Dynamic Links SDK callback | Resolve and match | Direct opens resolve a link. First opens after install call match. |
| Link analytics | Click, match, signup, and purchase attribution | Carry the Ferry click ID into the product events that matter. |
Use payload.data as the contract between a link and your app. A link to a
summer offer might carry { "screen": "offer", "campaign": "summer" }.
Your app owns the final navigation logic, so it can evolve without baking fragile
routes into every campaign URL.
Rebuild the domain and links.
Create a Ferry project with your iOS bundle ID, Android package name, store destinations, and desktop fallback. Then choose the hostname your links will use.
Choose a new hostname.
Google does not allow page.link domains to be transferred. Claim a Ferry
hostname such as https://acme.feryl.io, or connect a custom hostname
you own.
Keep the public address.
Recreate the important slugs in Ferry, verify the new configuration, then change
DNS. Keeping https://go.acme.com avoids replacing every printed or published
URL.
Create permanent campaign links with a server-side sk_ key or in the dashboard.
Never put that key in an app. Your mobile app uses a project-scoped
pk_ key for resolve, match, and restricted event reporting.
{
"projectId": "YOUR_PROJECT_ID",
"domainId": "YOUR_DOMAIN_ID",
"slug": "summer",
"payload": {
"data": {
"screen": "offer",
"campaign": "summer"
}
},
"campaign": {
"source": "newsletter",
"medium": "email",
"name": "summer"
}
} Replace the app integration in two paths.
Deep linking has two moments, and both need to be tested. The direct path opens an installed app. The deferred path sends a user to the store, then restores the link payload on first launch after install.
App already installed
Configure Universal Links on iOS and App Links on Android. Ferry serves the AASA
and assetlinks.json files for the project’s hostname. When the app receives
the URL, resolve it and route from the returned payload.
App installed after click
On the first launch, call POST /v1/match. Android can pass the Play
Install Referrer. iOS can pass an App Clip or pasteboard click ID when available,
with a coarse fingerprint as a probabilistic fallback.
Ferry returns a match method and confidence with the payload. Deterministic signals are preferred. If a fingerprint could refer to more than one click, Ferry returns no match instead of guessing.
{
"matched": true,
"method": "install_referrer",
"confidence": { "kind": "deterministic" },
"clickId": "clk_3Qm8xR2v",
"payload": {
"data": { "screen": "offer", "campaign": "summer" }
}
}
Keep the returned clickId. Send it when the user signs up or makes a
purchase so the dashboard can connect the outcome to the link and campaign, not just
count the open.
Cut over only after the full journey passes.
Use a staging hostname or an unpublished slug while the app change is under review. Test real devices where possible. Browsers, in-app web views, stores, and operating systems do not all behave like a simulator.
An installed iOS app opens the intended screen from the link.
An installed Android app opens the intended screen from the link.
A device without the app reaches the correct App Store or Play Store page.
The first launch after install receives the expected payload.
A desktop visitor reaches the configured web fallback.
A signup or purchase appears against the right link and campaign.
For a custom domain, lower DNS TTL before the cutover if your provider allows it. Recreate the important slugs, verify the well-known files, ship the app update, and only then point the hostname at Ferry. Keep a rollback note until traffic and attribution look normal.
For old page.link URLs, there is no DNS switch to make. Replace them at every
touchpoint you control, publish app and support messaging for the rest, and use campaign
reports to find high-value sources that still send traffic to dead links.
The goal is not link parity. It is journey continuity.
A successful migration leaves users with a working route, your app with a small and explicit payload contract, and your team with attribution from the first click to the outcome that matters.