Complete guide to implementing deep links and universal links in Capacitor apps. Covers iOS Universal Links, Android App Links, custom URL schemes, and navigation handling. Use this skill when users need to open their app from links.
Implement deep links, universal links, and app links in Capacitor apps.
| Type | Platform | Format | Requires Server |
|---|---|---|---|
| Custom URL Scheme | Both | myapp://path | No |
| Universal Links | iOS | https://myapp.com/path | Yes |
| App Links | Android | https://myapp.com/path | Yes |
bun add @capacitor/app
bunx cap sync
import { App } from '@capacitor/app';
// Listen for deep link opens
App.addListener('appUrlOpen', (event) => {
console.log('App opened with URL:', event.url);
// Parse and navigate
const url = new URL(event.url);
handleDeepLink(url);
});
function handleDeepLink(url: URL) {
// Custom scheme: myapp://product/123
// Universal link: https://myapp.com/product/123
const path = url.pathname || url.host + url.pathname;
// Route based on path
if (path.startsWith('/product/')) {
const productId = path.split('/')[2];
navigateTo(`/product/${productId}`);
} else if (path.startsWith('/user/')) {
const userId = path.split('/')[2];
navigateTo(`/profile/${userId}`);
} else if (path === '/login') {
navigateTo('/login');
} else {
navigateTo('/');
}
}
<!-- ios/App/App/Info.plist -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourcompany.yourapp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
<string>myapp-dev</string>
</array>
</dict>
</array>
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- Deep link intent filter -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="myapp" />
</intent-filter>
</activity>
# iOS Simulator
xcrun simctl openurl booted "myapp://product/123"
# Android
adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
In Xcode:
applinks:myapp.comHost at https://myapp.com/.well-known/apple-app-site-association:
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.yourcompany.yourapp",
"paths": [
"/product/*",
"/user/*",
"/invite/*",
"NOT /api/*"
]
}
]
}
}
Requirements:
application/json<!-- ios/App/App/Info.plist -->
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:myapp.com</string>
<string>applinks:www.myapp.com</string>
</array>
# Validate AASA file
curl -I https://myapp.com/.well-known/apple-app-site-association
# Check Apple CDN cache
curl "https://app-site-association.cdn-apple.com/a/v1/myapp.com"
Host at https://myapp.com/.well-known/assetlinks.json:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourcompany.yourapp",
"sha256_cert_fingerprints": [
"AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99"
]
}
}
]
# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release keystore
keytool -list -v -keystore release.keystore -alias your-alias
# From APK
keytool -printcert -jarfile app-release.apk
<!-- android/app/src/main/AndroidManifest.xml -->
<activity android:name=".MainActivity">
<!-- App Links intent filter -->
<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" />
<data android:host="myapp.com" />
<data android:pathPrefix="/product" />
<data android:pathPrefix="/user" />
<data android:pathPrefix="/invite" />
</intent-filter>
</activity>
# Validate assetlinks.json
curl https://myapp.com/.well-known/assetlinks.json
# Use Google's validator