Capacitor Deep Linking

by cap-go2cfb277a1488No license72 starsListed Oct 8, 2026Updated Oct 8, 2026Repository updated today

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.

Instructions onlySoftware Development
AI-generated overview

Guide for implementing deep links, universal links and app links in Capacitor mobile apps.

What it does
This skill provides instructions for adding deep linking to Capacitor apps, covering custom URL schemes, iOS Universal Links and Android App Links. It shows how to configure Info.plist, AndroidManifest.xml, apple-app-site-association and assetlinks.json files, and how to handle incoming URLs with the Capacitor App plugin. It also includes routing integration examples for React Router and Vue Router, OAuth callback handling, query parameter parsing, deferred deep links, testing commands and troubleshooting tips.
When to use it
Use it when a user wants to open their Capacitor app from a link, set up universal links or app links, define a custom URL scheme, or handle share and invite links. It is also relevant when debugging links that open in the browser instead of the app.
Requirements
Requires a Capacitor mobile project with the @capacitor/app plugin installed, plus iOS and Android platform configuration files. Universal Links and App Links need HTTPS hosting of association files on the app's domain. No scripts are included; the skill is instructions and code examples only.

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

TypePlatformFormatRequires Server
Custom URL SchemeBothmyapp://pathNo
Universal LinksiOShttps://myapp.com/pathYes
App LinksAndroidhttps://myapp.com/pathYes

Quick Start

Install Plugin

bash
npm install @capacitor/appnpx cap sync

Handle Deep Links

typescript
import { App } from '@capacitor/app';
// Listen for deep link opensApp.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

xml
<!-- 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

xml
<!-- 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

bash
# iOS Simulatorxcrun simctl openurl booted "myapp://product/123"
# Androidadb shell am start -a android.intent.action.VIEW -d "myapp://product/123"

Universal Links (iOS)

1. Enable Associated Domains

In Xcode:

  1. Select App target
  2. Signing & Capabilities
    • Capability > Associated Domains
  3. Add: applinks:myapp.com

2. Create apple-app-site-association

Host at https://myapp.com/.well-known/apple-app-site-association:

json
{  "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

xml
<!-- 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

bash
# Validate AASA filecurl -I https://myapp.com/.well-known/apple-app-site-association
# Check Apple CDN cachecurl "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:

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

bash
# Debug keystorekeytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
# Release keystorekeytool -list -v -keystore release.keystore -alias your-alias
# From APKkeytool -printcert -jarfile app-release.apk

2. AndroidManifest.xml

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

bash
# Validate assetlinks.jsoncurl https://myapp.com/.well-known/assetlinks.json
# Use Google's validatorhttps://developers.google.com/digital-asset-links/tools/generator
# Check link handling on deviceadb shell pm get-app-links com.yourcompany.yourapp

Advanced Routing

React Router Integration

typescript
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

typescript
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:

typescript
import { App } from '@capacitor/app';import { Preferences } from '@capacitor/preferences';
// On first launch, check for deferred linkasync 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

typescript
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

typescript
// Handle OAuth redirectApp.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

ScenarioCommand
Custom schememyapp://path
Universal link cold startTap link with app closed
Universal link warm startTap link with app in background
Universal link in SafariType URL in Safari
App link cold startTap link with app closed
App link in ChromeTap link in Chrome

Debug Tools

bash
# iOS: Check associated domains entitlementcodesign -d --entitlements - App.app | grep associated-domains
# iOS: Reset Universal Links cachexcrun simctl erase all
# Android: Check verified linksadb shell dumpsys package d | grep -A5 "Package: com.yourcompany.yourapp"

Common Issues

IssueSolution
Universal Links not workingCheck AASA file, SSL, entitlements
App Links not verifiedCheck assetlinks.json, fingerprint
Links open in browserCheck intent-filter, autoVerify
Cold start not handledUse App.getLaunchUrl()
Simulator issuesReset simulator, rebuild app

Resources

Source and attribution

Source:cap-go/capgo-skillsinskills/capacitor-deep-linkingat commit2cfb277

License: No license

Content belongs to its original authors. SourceWeft indexes it from a public repository.

Report or request removal