Deep links are critical for user experience, but they're notoriously difficult to debug when things go wrong. This comprehensive guide walks you through systematically diagnosing and fixing deep linking issues in Flutter apps.
Deep links often fail silently, meaning your app receives no error messages when a link doesn't work. The user just gets sent to the home screen instead of the intended deep-linked screen. This happens because deep linking involves multiple systems:
When any part of this chain breaks, the entire deep link fails. Let's walk through how to systematically debug each component.
iOS Requires HTTPS
Universal Links only work with HTTPS domains. HTTP will not work, even during testing.
The Apple App Site Association (AASA) file is the foundation of iOS Universal Links. Use our validator to ensure it's correctly formatted:
Your Xcode project must have the Associated Domains entitlement configured:
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:example.com</string>
<string>applinks:www.example.com</string>
</array>To do this in Xcode: Select your target → Signing & Capabilities → Add Capability → Associated Domains → Add your domain with the applinks: prefix.
Your AASA file must be hosted at exactly: https://example.com/.well-known/apple-app-site-association
# Test if your AASA file is accessible (replace example.com) curl -v https://example.com/.well-known/apple-app-site-association
Check the response headers. The Content-Type must be:
Content-Type: application/json
iOS Simulator does NOT validate AASA files or properly handle Universal Links. You must test on a real device:
# Install your app on a real device # Then test the link in Safari # For real device: # 1. Open Safari # 2. Paste your deep link URL # 3. Tap the link # 4. Your app should launch with the route
iOS aggressively caches AASA files. After updating your AASA file:
iOS Caching Gotcha
If your deep link worked once but now doesn't after updating AASA, it's likely a cache issue. Force refresh by fully uninstalling and reinstalling.
Android App Links require the assetlinks.json file, correct intent filters, and matching app signatures. Let's go through each:
Use our validator to ensure your assetlinks.json is correctly formatted:
Your assetlinks.json needs the SHA-256 fingerprint of your signing certificate. Get it differently for debug vs. release:
For Debug Key:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android | grep "SHA1"
For Release Key:
keytool -list -v -keystore /path/to/your/keystore.jks -alias your-alias
Critical: Debug vs Release Keys
Your debug and release keys have different SHA-256 fingerprints. If you test with the debug APK but your assetlinks.json has the release fingerprint, deep links won't work. Use the correct fingerprint for each build type.
Your intent filter must have autoVerify="true":
<activity
android:name=".MainActivity"
android:launchMode="singleTop">
<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="example.com"
android:pathPrefix="/deep-link" />
</intent-filter>
</activity>Your assetlinks.json must be hosted at: https://example.com/.well-known/assetlinks.json
curl -v https://example.com/.well-known/assetlinks.json
Test deep links directly using Android Debug Bridge:
adb shell am start -W -a android.intent.action.VIEW -d "https://example.com/deep-link" com.your.package
If the intent filter matches correctly, you'll see the app launch. Check Logcat for any errors:
adb logcat | grep "your-package-name"
Deferred deep links (links that fire after app installation) have additional complexity. Here's how to debug:
Deferred deep links work within an "attribution window" (typically 24-48 hours). If the user installs the app after the attribution window closes, the deferred link won't fire. You can:
Your Redirectly SDK must be initialized early in your app lifecycle, before checking for deep links:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Redirectly FIRST
await Redirectly.initialize(apiKey: "YOUR_API_KEY");
// Then check for deferred links
await Redirectly.checkDeepLink((deepLink) {
// Handle the deferred deep link
print("Deferred link: ${deepLink.url}");
});
runApp(MyApp());
}Deferred deep links require a valid API key. Verify it's correct in your Flutter code:
Redirectly.initialize() callYour link must be created with the deferred parameter set. Check in your dashboard:
The Redirectly dashboard shows real-time attribution data:
Testing Deferred Links Locally
To test deferred links on your computer, use Redirectly's test endpoint or create a temporary link and immediately uninstall/reinstall your debug app on a connected device.
The iOS Simulator has limited deep link support. Use the command:
xcrun simctl openurl booted "https://example.com/deep-link"
However, this doesn't validate AASA files or truly test Universal Links. For accurate testing, you must use a real device.
Android Emulator supports deep link testing better than iOS Simulator:
adb shell am start -W -a android.intent.action.VIEW -d "https://example.com/deep-link" com.your.package
Redirectly's dashboard shows link click analytics in real-time:
Go through this checklist systematically when debugging deep links:
Deep link debugging requires methodically checking each layer of the system. Start with validating your AASA and assetlinks.json files, then verify your Xcode and Android configurations, and finally test thoroughly on real devices.
If you're still having issues, use these resources to diagnose further: