Agent skill
capacitor-deep-linking
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.
Install this agent skill to your Project
npx add-skill https://github.com/Cap-go/capgo-skills/tree/main/skills/capacitor-deep-linking
SKILL.md
Deep Linking in Capacitor
Implement deep links, universal links, and app links in Capacitor apps.
When to Use This Skill
- User wants deep links
- User needs universal links
- User asks about URL schemes
- User wants to open app from links
- User needs share links
Types of Deep Links
| 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 |
Quick Start
Install Plugin
npm install @capacitor/app
npx cap sync
Handle Deep Links
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('/');
}
}
Custom URL Scheme
iOS Configuration
<!-- 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 Configuration
<!-- 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>
Test Custom Scheme
# iOS Simulator
xcrun simctl openurl booted "myapp://product/123"
# Android
adb shell am start -a android.intent.action.VIEW -d "myapp://product/123"
Universal Links (iOS)
1. Enable Associated Domains
In Xcode:
- Select App target
- Signing & Capabilities
-
- Capability > Associated Domains
- Add:
applinks:myapp.com
2. Create apple-app-site-association
Host 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:
- Served over HTTPS
- Content-Type:
application/json - No redirects
- File at root domain
3. Info.plist
<!-- 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>
Verify Universal Links
# 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"
App Links (Android)
1. Create assetlinks.json
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"
]
}
}
]
Get SHA256 Fingerprint
# 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
2. AndroidManifest.xml
<!-- 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>
Verify App Links
# Validate assetlinks.json
curl https://myapp.com/.well-known/assetlinks.json
# Use Google's validator
https://developers.google.com/digital-asset-links/tools/generator
# Check link handling on device
adb shell pm get-app-links com.yourcompany.yourapp
Advanced Routing
React Router Integration
import { App } from '@capacitor/app';
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';
function DeepLinkHandler() {
const history = useHistory();
useEffect(() => {
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
const path = getPathFromUrl(url);
// Navigate using React Router
history.push(path);
});
// Check if app was opened with URL
App.getLaunchUrl().then((result) => {
if (result?.url) {
const url = new URL(result.url);
const path = getPathFromUrl(url);
history.push(path);
}
});
}, []);
return null;
}
function getPathFromUrl(url: URL): string {
// Handle both custom scheme and https
if (url.protocol === 'myapp:') {
return '/' + url.host + url.pathname;
}
return url.pathname + url.search;
}
Vue Router Integration
import { App } from '@capacitor/app';
import { useRouter } from 'vue-router';
import { onMounted } from 'vue';
export function useDeepLinks() {
const router = useRouter();
onMounted(async () => {
App.addListener('appUrlOpen', (event) => {
const path = parseDeepLink(event.url);
router.push(path);
});
const launchUrl = await App.getLaunchUrl();
if (launchUrl?.url) {
const path = parseDeepLink(launchUrl.url);
router.push(path);
}
});
}
Deferred Deep Links
Handle links when app wasn't installed:
import { App } from '@capacitor/app';
import { Preferences } from '@capacitor/preferences';
// On first launch, check for deferred link
async function checkDeferredDeepLink() {
const { value: isFirstLaunch } = await Preferences.get({ key: 'firstLaunch' });
if (isFirstLaunch !== 'false') {
await Preferences.set({ key: 'firstLaunch', value: 'false' });
// Check with your attribution service
const deferredLink = await fetchDeferredLink();
if (deferredLink) {
handleDeepLink(new URL(deferredLink));
}
}
}
Query Parameters
App.addListener('appUrlOpen', (event) => {
const url = new URL(event.url);
// Get query parameters
const source = url.searchParams.get('source');
const campaign = url.searchParams.get('campaign');
const referrer = url.searchParams.get('ref');
// Track attribution
analytics.logEvent('deep_link_open', {
path: url.pathname,
source,
campaign,
referrer,
});
// Navigate with state
navigateTo(url.pathname, {
state: { source, campaign, referrer },
});
});
OAuth Callback Handling
// Handle OAuth redirect
App.addListener('appUrlOpen', async (event) => {
const url = new URL(event.url);
if (url.pathname === '/oauth/callback') {
const code = url.searchParams.get('code');
const state = url.searchParams.get('state');
const error = url.searchParams.get('error');
if (error) {
handleOAuthError(error);
return;
}
if (code && validateState(state)) {
await exchangeCodeForToken(code);
navigateTo('/home');
}
}
});
Testing
Test Matrix
| Scenario | Command |
|---|---|
| Custom scheme | myapp://path |
| Universal link cold start | Tap link with app closed |
| Universal link warm start | Tap link with app in background |
| Universal link in Safari | Type URL in Safari |
| App link cold start | Tap link with app closed |
| App link in Chrome | Tap link in Chrome |
Debug Tools
# iOS: Check associated domains entitlement
codesign -d --entitlements - App.app | grep associated-domains
# iOS: Reset Universal Links cache
xcrun simctl erase all
# Android: Check verified links
adb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"
Common Issues
| Issue | Solution |
|---|---|
| Universal Links not working | Check AASA file, SSL, entitlements |
| App Links not verified | Check assetlinks.json, fingerprint |
| Links open in browser | Check intent-filter, autoVerify |
| Cold start not handled | Use App.getLaunchUrl() |
| Simulator issues | Reset simulator, rebuild app |
Resources
- Capacitor App Plugin: https://capacitorjs.com/docs/apis/app
- Universal Links Guide: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
- Android App Links: https://developer.android.com/training/app-links
- Digital Asset Links Validator: https://developers.google.com/digital-asset-links/tools/generator
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
konsta-ui
Guide to using Konsta UI for pixel-perfect iOS and Material Design components in Capacitor apps. Works with React, Vue, and Svelte. Use this skill when users want native-looking UI without Ionic, or prefer a lighter framework.
capacitor-plugin-upgrades
Guides the agent through upgrading a Capacitor plugin to a newer major version. Covers dependency alignment, native platform changes, example app verification, and multi-version jumps. Do not use for app project upgrades or non-Capacitor plugin frameworks.
capacitor-testing
Complete testing guide for Capacitor apps covering unit tests, integration tests, E2E tests, and native testing. Includes Jest, Vitest, Playwright, Appium, and native testing frameworks. Use this skill when users need to test their mobile apps.
capacitor-plugins
Official Capacitor package guide plus Capgo ecosystem plugin recommendations. Use this skill when users need native functionality, want the right official Capacitor package, or need a stronger Capgo/community plugin when the official package is missing or too limited.
capgo-release-management
Guides the agent through Capgo OTA release workflows including bundle uploads, compatibility checks, channels, cleanup, and encryption key setup. Use when managing Capgo bundle and channel operations. Do not use for native build requests or organization administration.
ionic-enterprise-sdk-migration
Guides the agent through migrating Capacitor apps from Ionic Enterprise SDK plugins to Capgo and Capacitor alternatives. Covers dependency detection, API replacement, local storage changes, and platform cleanup. Do not use for generic Capacitor version upgrades or Capgo live updates.
Didn't find tool you were looking for?