Migrating from Firebase Dynamic Links to LinkForty
Firebase Dynamic Links (FDL) was deprecated by Google on August 25, 2025 and will shut down completely on August 25, 2025. If you're using Firebase Dynamic Links, you need to migrate to an alternative solution.
This guide provides a complete migration path from Firebase Dynamic Links to LinkForty.
Why Firebase Dynamic Links Was Deprecated
Google announced the deprecation of Firebase Dynamic Links citing:
- Low adoption compared to other Firebase products
- High maintenance cost for Google
- Platform-native alternatives (Universal Links, App Links) now mature
- Shift in strategy - Google recommending platform-native solutions
Official announcement: Firebase Blog (August 2025)
Firebase Dynamic Links Shutdown Timeline
| Date | Event |
|---|---|
| August 25, 2025 | Deprecation announced, new link creation disabled |
| August 25, 2025 | Existing links continue to work (redirect only) |
| August 25, 2025 | Full shutdown - all links stop working |
⚠️ CRITICAL: If you don't migrate by August 25, 2025, all your Firebase Dynamic Links will stop working (404 errors).
LinkForty vs Firebase Dynamic Links
Feature Comparison
| Feature | Firebase Dynamic Links | LinkForty |
|---|---|---|
| Pricing | Free | Free (self-hosted) or $29/month (cloud) |
| Status | ❌ Deprecated (shutting down) | ✅ Active, growing |
| Short Links | ✅ Yes (page.link domain) | ✅ Yes (custom domain) |
| Deferred Deep Linking | ✅ Yes | ✅ Yes (70-80% accuracy) |
| Analytics | Basic | ✅ Comprehensive (click tracking, attribution, conversions) |
| Custom Domains | ❌ No (only page.link) | ✅ Yes (bring your own domain) |
| Universal/App Links | ✅ Auto-generated | ✅ Auto-generated |
| Open Source | ❌ No | ✅ Yes (MIT license) |
| Self-Hosted Option | ❌ No | ✅ Yes |
| Data Ownership | ❌ Google owns data | ✅ You own data (self-hosted) |
| UTM Tracking | ✅ Yes | ✅ Yes |
| QR Codes | ❌ No | ✅ Yes |
| Webhooks | ❌ No | ✅ Yes |
| API Access | ✅ Limited | ✅ Full REST API |
| Team Collaboration | ❌ No | ✅ Yes (cloud) |
What You Gain with LinkForty
✅ More Features:
- Custom domains (vs Firebase's
page.linkonly) - QR code generation
- Webhooks for real-time events
- Team collaboration (cloud)
- Advanced analytics (conversion tracking, campaign ROI)
✅ Data Ownership:
- Self-hosted option (full control)
- No vendor lock-in (open source)
- Export data anytime
✅ Privacy-First:
- GDPR compliant by design
- No third-party data sharing
- Transparent attribution
✅ Cost-Effective:
- Free forever (self-hosted)
- $29/month (managed cloud) - predictable pricing
- No usage-based pricing surprises
What You Lose (and How LinkForty Handles It)
❌ Firebase Console Integration
- Firebase DL was integrated into Firebase Console
- LinkForty solution: Dedicated dashboard with better UX
❌ Automatic Google Analytics Integration
- Firebase DL sent events to Google Analytics automatically
- LinkForty solution: Webhooks to send events to any analytics platform (including GA)
❌ page.link Domain
- Firebase provided
yourapp.page.linkdomain - LinkForty solution: Use your own domain (better branding) like
go.yourapp.com
❌ Firebase Auth Integration
- Firebase DL integrated with Firebase Auth for attribution
- LinkForty solution: Works with any auth system via API
Migration Guide: Step-by-Step
Phase 1: Setup LinkForty (15 minutes)
Option A: LinkForty Cloud (Fastest)
1. Create account:
Visit: https://linkforty.com
Sign up (no credit card required)
Create organization
2. Configure app details:
Settings → App Configuration
iOS:
- Team ID: [Your Apple Team ID]
- Bundle ID: com.yourapp
- App Store URL: https://apps.apple.com/app/yourapp/id123456
Android:
- Package Name: com.yourapp
- SHA-256 Fingerprint: [Your keystore fingerprint]
- Google Play URL: https://play.google.com/store/apps/details?id=com.yourapp
3. Set up custom domain (optional but recommended):
Settings → Custom Domains
Add domain: go.yourapp.com
Configure DNS: CNAME go.yourapp.com → your-org.linkforty.app
4. Get API key:
Settings → API Keys
Create API Key → Copy and save securely
Option B: Self-Hosted (Free)
1. Install LinkForty Core:
npm install @linkforty/core
2. Set up infrastructure:
# PostgreSQL (required)
docker run -d \
--name linkforty-postgres \
-e POSTGRES_PASSWORD=yourpassword \
-e POSTGRES_DB=linkforty \
-p 5432:5432 \
postgres:15
# Redis (optional, improves performance)
docker run -d \
--name linkforty-redis \
-p 6379:6379 \
redis:7
3. Configure environment:
# .env
DATABASE_URL=postgresql://postgres:yourpassword@localhost:5432/linkforty
REDIS_URL=redis://localhost:6379
JWT_SECRET=your-super-secret-key-change-this
FRONTEND_URL=https://yourapp.com
4. Run migrations:
cd node_modules/@linkforty/core
npm run migrate
5. Start server:
npm start
Phase 2: Create Links in LinkForty (Replace Firebase Links)
Firebase Dynamic Links Structure:
https://yourapp.page.link/abc123
Parameters:
- link: https://yourapp.com/products/123
- apn: com.yourapp (Android package)
- ibi: com.yourapp (iOS bundle ID)
- isi: 123456 (App Store ID)
- utm_source: instagram
- utm_campaign: summer-sale
LinkForty Equivalent:
Create link template first:
Dashboard → Settings → Link Templates → Create Template
Name: Default Template
iOS URL: https://apps.apple.com/app/yourapp/id123456
Android URL: https://play.google.com/store/apps/details?id=com.yourapp
Attribution Window: 7 days
Create link:
Dashboard → Links → Create Link
Template: Default Template
Original URL: https://yourapp.com/products/123
Title: Product 123 Campaign
UTM Source: instagram
UTM Campaign: summer-sale
Generated link: https://go.yourapp.com/abc123
Or use API:
curl -X POST https://api.linkforty.com/api/links \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"templateId": "template_abc123",
"originalUrl": "https://yourapp.com/products/123",
"title": "Product 123 Campaign",
"utmParams": {
"source": "instagram",
"campaign": "summer-sale"
}
}'
Response:
{
"id": "link_xyz789",
"shortCode": "abc123",
"shortUrl": "https://go.yourapp.com/abc123",
"originalUrl": "https://yourapp.com/products/123"
}
Phase 3: Migrate SDK (30-60 minutes)
iOS Migration
Remove Firebase Dynamic Links SDK:
Before (Firebase):
// Podfile
pod 'Firebase/DynamicLinks'
// AppDelegate.swift
import Firebase
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FirebaseApp.configure()
return true
}
// Handle Firebase Dynamic Link
func application(_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
handleDynamicLink(dynamicLink)
return true
}
return false
}
func handleDynamicLink(_ dynamicLink: DynamicLink) {
guard let url = dynamicLink.url else { return }
// Route to content
navigateToContent(url: url)
}
After (LinkForty):
# Install LinkForty SDK
# Coming soon: pod 'LinkFortySDK'
# For now, use REST API approach (see below)
REST API approach (works now):
import Foundation
class LinkFortySDK {
static let shared = LinkFortySDK()
private let apiKey = "your-api-key"
private let baseUrl = "https://api.linkforty.com"
func checkAttribution() async -> AttributionData? {
// Collect device fingerprint
let fingerprint = collectFingerprint()
// Send to LinkForty
var request = URLRequest(url: URL(string: "\(baseUrl)/api/attribution/match")!)
request.httpMethod = "POST"
request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let body = [
"fingerprint": fingerprint
]
request.httpBody = try? JSONEncoder().encode(body)
guard let (data, _) = try? await URLSession.shared.data(for: request),
let response = try? JSONDecoder().decode(AttributionResponse.self, from: data),
response.matched else {
return nil
}
return response.attribution
}
private func collectFingerprint() -> [String: Any] {
let screen = UIScreen.main
return [
"screen_width": Int(screen.bounds.width * screen.scale),
"screen_height": Int(screen.bounds.height * screen.scale),
"language": Locale.current.languageCode ?? "en",
"timezone": TimeZone.current.identifier,
"os_version": UIDevice.current.systemVersion,
"device_model": UIDevice.current.model
]
}
}
struct AttributionResponse: Codable {
let matched: Bool
let attribution: AttributionData?
}
struct AttributionData: Codable {
let destination: String
let utmSource: String?
let utmCampaign: String?
let customParams: [String: String]?
}
Use in AppDelegate:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Remove: FirebaseApp.configure()
// Check for deferred deep link attribution
Task {
if let attribution = await LinkFortySDK.shared.checkAttribution() {
handleAttribution(attribution)
}
}
return true
}
func handleAttribution(_ attribution: AttributionData) {
// Route to destination
let path = attribution.destination
navigateToContent(path: path)
// Track campaign
if let campaign = attribution.utmCampaign {
analytics.track("attributed_install", properties: ["campaign": campaign])
}
}
Handle Universal Links (same as before):
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
// LinkForty handles Universal Links automatically
// Just route to content
navigateToContent(url: url)
return true
}
Android Migration
Remove Firebase Dynamic Links SDK:
Before (Firebase):
// build.gradle
dependencies {
implementation 'com.google.firebase:firebase-dynamic-links:21.1.0'
}
// MainActivity.kt
import com.google.firebase.dynamiclinks.FirebaseDynamicLinks
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Handle Firebase Dynamic Link
FirebaseDynamicLinks.getInstance()
.getDynamicLink(intent)
.addOnSuccessListener { pendingDynamicLinkData ->
val deepLink = pendingDynamicLinkData?.link
deepLink?.let { handleDeepLink(it) }
}
}
fun handleDeepLink(uri: Uri) {
val path = uri.path
// Route to content
navigateToContent(path)
}
After (LinkForty):
# Install LinkForty SDK
# Coming soon: implementation 'com.linkforty:sdk:1.0.0'
# For now, use REST API approach (see below)
REST API approach (works now):
import kotlinx.coroutines.*
import okhttp3.*
import org.json.JSONObject
class LinkFortySDK(private val apiKey: String, private val baseUrl: String = "https://api.linkforty.com") {
private val client = OkHttpClient()
suspend fun checkAttribution(): AttributionData? = withContext(Dispatchers.IO) {
try {
val fingerprint = collectFingerprint()
val json = JSONObject().apply {
put("fingerprint", fingerprint)
}
val request = Request.Builder()
.url("$baseUrl/api/attribution/match")
.addHeader("Authorization", "Bearer $apiKey")
.addHeader("Content-Type", "application/json")
.post(RequestBody.create(
MediaType.parse("application/json"),
json.toString()
))
.build()
val response = client.newCall(request).execute()
val responseBody = response.body()?.string() ?: return@withContext null
val jsonResponse = JSONObject(responseBody)
if (jsonResponse.getBoolean("matched")) {
val attribution = jsonResponse.getJSONObject("attribution")
AttributionData(
destination = attribution.getString("destination"),
utmSource = attribution.optString("utmSource"),
utmCampaign = attribution.optString("utmCampaign")
)
} else {
null
}
} catch (e: Exception) {
null
}
}
private fun collectFingerprint(): JSONObject {
val displayMetrics = Resources.getSystem().displayMetrics
return JSONObject().apply {
put("screen_width", displayMetrics.widthPixels)
put("screen_height", displayMetrics.heightPixels)
put("language", Locale.getDefault().language)
put("timezone", TimeZone.getDefault().id)
put("os_version", Build.VERSION.RELEASE)
put("device_model", Build.MODEL)
}
}
}
data class AttributionData(
val destination: String,
val utmSource: String?,
val utmCampaign: String?
)
Use in MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Check for deferred deep link attribution
lifecycleScope.launch {
val sdk = LinkFortySDK(apiKey = "your-api-key")
val attribution = sdk.checkAttribution()
attribution?.let {
handleAttribution(it)
}
}
}
fun handleAttribution(attribution: AttributionData) {
// Route to destination
navigateToContent(attribution.destination)
// Track campaign
attribution.utmCampaign?.let { campaign ->
analytics.track("attributed_install", mapOf("campaign" to campaign))
}
}
Handle App Links (same as before):
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
intent?.data?.let { uri ->
// LinkForty handles App Links automatically
handleDeepLink(uri)
}
}
React Native Migration
Before (Firebase):
import dynamicLinks from '@react-native-firebase/dynamic-links';
useEffect(() => {
const unsubscribe = dynamicLinks().onLink(handleDynamicLink);
return () => unsubscribe();
}, []);
const handleDynamicLink = (link) => {
const url = link.url;
// Route to content
navigateToContent(url);
};
After (LinkForty):
# Install LinkForty SDK
npm install @linkforty/react-native-sdk
import LinkForty from '@linkforty/react-native-sdk';
import { Linking } from 'react-native';
useEffect(() => {
// Initialize LinkForty
LinkForty.init({
baseUrl: 'https://api.linkforty.com',
apiKey: 'your-api-key',
debug: __DEV__,
});
// Check for deferred deep link attribution
checkAttribution();
// Handle regular deep links
const subscription = Linking.addEventListener('url', ({ url }) => {
handleDeepLink(url);
});
return () => subscription.remove();
}, []);
const checkAttribution = async () => {
const attribution = await LinkForty.getAttributionData();
if (attribution && attribution.matched) {
// User came from a link, route to destination
console.log('Attribution:', attribution);
navigateToContent(attribution.destination);
// Track attributed install
analytics.track('attributed_install', {
campaign: attribution.utmCampaign,
source: attribution.utmSource,
});
}
};
const handleDeepLink = (url: string) => {
const path = url.replace('https://go.yourapp.com', '');
navigateToContent(path);
};
Phase 4: Update Link Generation (Replace Firebase API Calls)
Firebase Dynamic Links API:
Before:
// Firebase Dynamic Links REST API
const response = await fetch('https://firebasedynamiclinks.googleapis.com/v1/shortLinks?key=API_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
dynamicLinkInfo: {
domainUriPrefix: 'https://yourapp.page.link',
link: 'https://yourapp.com/products/123',
androidInfo: {
androidPackageName: 'com.yourapp',
},
iosInfo: {
iosBundleId: 'com.yourapp',
iosAppStoreId: '123456',
},
socialMetaTagInfo: {
socialTitle: 'Product Title',
socialDescription: 'Product description',
},
},
}),
});
const { shortLink } = await response.json();
// shortLink: https://yourapp.page.link/abc123
LinkForty API:
After:
const response = await fetch('https://api.linkforty.com/api/links', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
templateId: 'template_default', // Pre-configured with iOS/Android URLs
originalUrl: 'https://yourapp.com/products/123',
title: 'Product 123',
description: 'Product description',
utmParams: {
source: 'instagram',
campaign: 'summer-sale',
},
}),
});
const { shortUrl } = await response.json();
// shortUrl: https://go.yourapp.com/abc123
Phase 5: Migrate Analytics
Firebase Analytics Events:
Firebase Dynamic Links sent events to Firebase Analytics automatically:
dynamic_link_first_open- First app open from dynamic linkdynamic_link_app_open- Subsequent opens from dynamic link
LinkForty Webhooks:
LinkForty provides webhooks for real-time events:
1. Configure webhook:
Dashboard → Settings → Webhooks → Create Webhook
URL: https://yourapp.com/webhooks/linkforty
Events:
- link.clicked
- app.installed
- deeplink.matched
2. Receive events:
// Express.js webhook endpoint
app.post('/webhooks/linkforty', (req, res) => {
const { event, data } = req.body;
switch (event) {
case 'link.clicked':
// User clicked link
analytics.track('Link Clicked', {
linkId: data.linkId,
campaign: data.utmCampaign,
});
break;
case 'app.installed':
// User installed app (deferred deep link matched)
analytics.track('App Installed', {
linkId: data.linkId,
campaign: data.utmCampaign,
source: data.utmSource,
});
// Send to Google Analytics if needed
sendToGoogleAnalytics({
event: 'dynamic_link_first_open',
campaign: data.utmCampaign,
});
break;
case 'deeplink.matched':
// Deferred deep link successfully matched
analytics.track('Deeplink Matched', {
destination: data.destination,
});
break;
}
res.sendStatus(200);
});
Phase 6: Testing
1. Test link creation:
Create test link in LinkForty dashboard
Verify short URL works: https://go.yourapp.com/test123
2. Test with app installed:
1. Have app installed
2. Click test link
3. Verify app opens (not browser)
4. Verify routes to correct content
3. Test deferred deep linking (app NOT installed):
1. Uninstall app
2. Click test link
3. Redirected to App Store/Google Play ✓
4. Install app
5. Open app
6. Verify routes to correct content ✓
7. Verify attribution tracked in dashboard ✓
4. Test analytics:
1. Create test link with UTM parameters
2. Click link, install app
3. Check LinkForty dashboard → Analytics
4. Verify click and install tracked ✓
5. Check webhook endpoint
6. Verify events received ✓
Phase 7: Go Live
1. Update production links:
Replace all yourapp.page.link URLs with go.yourapp.com URLs
Update:
- Marketing campaigns
- Email templates
- Social media bios
- Print materials
- QR codes
2. Set up redirects (if needed):
If you have many existing Firebase Dynamic Links in the wild:
Option A: Let them expire (Firebase shuts down Aug 25, 2025)
Option B: Set up redirect service to catch old links and redirect to new LinkForty links
3. Monitor:
Dashboard → Analytics
- Click rates
- Install attribution
- Conversion rates
- Campaign performance
Migration Checklist
- Phase 1: Set up LinkForty (Cloud or Self-Hosted)
- Phase 2: Create link templates and test links
- Phase 3: Migrate mobile SDKs (iOS, Android, React Native)
- Phase 4: Update link generation (replace Firebase API calls)
- Phase 5: Set up analytics webhooks
- Phase 6: Test thoroughly (installed app + new installs)
- Phase 7: Update production links and go live
Common Migration Issues
Issue 1: "Universal Links / App Links not working"
Cause: Verification files not set up correctly
Solution:
- Check LinkForty generated verification files:
https://go.yourapp.com/.well-known/apple-app-site-associationhttps://go.yourapp.com/.well-known/assetlinks.json
- Verify accessible (200 OK, no redirects)
- Uninstall and reinstall app (iOS caches AASA file)
Issue 2: "Attribution accuracy lower than expected"
Cause: Fingerprinting vs Firebase's device ID approach
Solution:
- Fingerprinting achieves 70-80% accuracy (industry standard for privacy-friendly attribution)
- Firebase used device IDs (higher accuracy but requires user permission post-iOS 14.5)
- To improve: Shorten attribution window, use Universal/App Links
Issue 3: "Missing Firebase Console integration"
Cause: LinkForty is standalone (not part of Firebase)
Solution:
- Use LinkForty dashboard for link management
- Use webhooks to send events to Firebase Analytics (if needed)
Cost Comparison
Firebase Dynamic Links:
- Pricing: Free
- Status: Shutting down (no longer an option)
LinkForty:
- Self-Hosted: Free forever (MIT license)
- Cloud: $29/month (unlimited links, unlimited clicks)
ROI: $29/month to replace a shutdown service with MORE features is excellent value.
Support & Help
Need migration help?
- 📚 Documentation: docs.linkforty.com
- 💬 Community Support: GitHub Discussions
- 📧 Email Support: support@linkforty.com (Cloud users get priority)
- 🛠️ Migration Service: Contact sales@linkforty.com for white-glove migration assistance
Frequently Asked Questions
When is Firebase Dynamic Links shutting down?
August 25, 2025 - All Firebase Dynamic Links will stop working (404 errors).
Can I keep using Firebase Dynamic Links until shutdown?
Yes, existing links will work until August 25, 2025. However, you cannot create new Firebase Dynamic Links after August 25, 2025.
Will my old Firebase links redirect to LinkForty automatically?
No. You need to:
- Create equivalent links in LinkForty
- Update your marketing materials with new LinkForty URLs
- (Optional) Set up redirect service for old Firebase URLs if needed
How long does migration take?
- Simple apps: 1-2 days (SDK update + testing)
- Complex apps: 1 week (SDK update + link migration + analytics setup + testing)
Is LinkForty more expensive than Firebase Dynamic Links?
Firebase was free but is shutting down. LinkForty offers:
- Free (self-hosted) - same cost as Firebase, more control
- $29/month (cloud) - managed service with better features than Firebase ever had
Can I use LinkForty with Firebase Authentication?
Yes! LinkForty works with any authentication system. Use webhooks to send attribution data to your Firebase project.
Does LinkForty support custom domains like Firebase?
Better! Firebase only allowed yourapp.page.link. LinkForty lets you use any domain you own (e.g., go.yourapp.com, link.yourapp.com).
Summary
Firebase Dynamic Links is shutting down on August 25, 2025. You must migrate before this date or all your links will stop working.
LinkForty provides:
- ✅ All Firebase Dynamic Links features (and more)
- ✅ Simple migration path (1-2 days for most apps)
- ✅ Better analytics and webhooks
- ✅ Custom domains
- ✅ Open source & self-hostable
- ✅ Privacy-first approach
- ✅ Free (self-hosted) or affordable ($29/month cloud)
Ready to migrate?
Questions?