MCPInstall

capacitor-deep-linking

Implements and debugs deep links in Capacitor apps - custom URL schemes, iOS Universal Links (apple-app-site-association, Associated Domains entitlement), Android App Links (assetlinks.json, autoVerify), and routing via @capacitor/app appUrlOpen + App.getLaunchUrl(). Covers the Capacitor 8.5+ UIScene change where AppDelegate application(:open:options:) and continue userActivity stop being called and custom handling must move to SceneDelegate, plus cold vs warm launch testing. Use when links open Safari/Chrome instead of the app, appUrlOpen never fires, the launch URL is lost on cold start, or OAuth redirects do not return to the app. Do not use for the full UIScene migration (capacitor-uiscene-migration), push notification taps (capacitor-push-notifications), or social login plugin setup (capacitor-plugins).

Installs
1.3K
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-deep-linking
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads capacitor-deep-linking automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Deep Linking in Capacitor

Route custom-scheme links, iOS Universal Links, and Android App Links into the web app.

When to Use

TRIGGER when:

  • Adding myapp:// schemes, Universal Links, or App Links to a Capacitor app
  • Links open the browser instead of the app, or open the app but land on the home page
  • appUrlOpen fires on warm open but the URL is missing on cold launch (or the reverse)
  • Deep links broke after updating to Capacitor 8.5+ / adopting SceneDelegate.swift
  • OAuth / magic-link / payment redirects must come back into the app
  • adb shell pm get-app-links shows none or 1024 instead of verified

Do not use:

  • Full AppDelegate -> SceneDelegate migration of an app: capacitor-uiscene-migration
  • Notification tap routing: capacitor-push-notifications
  • Native log capture while debugging: ios-android-logs
  • Sign in with Apple / Google plugin config: capacitor-plugins

Link Types

TypePlatformFormatNeeds hosted fileVerified ownership
Custom schemebothmyapp://product/123nono (any app can claim it)
Universal LinksiOShttps://example.com/product/123/.well-known/apple-app-site-associationyes
App LinksAndroid 6+https://example.com/product/123/.well-known/assetlinks.jsonyes

Prefer verified https links for anything security-sensitive (OAuth, magic links). Custom schemes can be hijacked by another app.

Workflow

  1. Inspect first. Read capacitor.config.*, ios/App/App/Info.plist, ios/App/App/*.entitlements, ios/App/App/AppDelegate.swift, ios/App/App/SceneDelegate.swift (if present), android/app/src/main/AndroidManifest.xml, and the @capacitor/app version in package.json.
  2. Decide the iOS lifecycle path.
    • UIApplicationSceneManifest present in Info.plist (Capacitor 8.5+ template or migrated app) -> URL events arrive in SceneDelegate. Any custom code in AppDelegate application(_:open:options:) or application(_:continue:restorationHandler:) is dead code. See references/ios-scene-delegate.md.
    • No scene manifest -> AppDelegate path still works on 8.5 core, but Xcode 27 / Capacitor 9 require the scene lifecycle. Hand the migration to capacitor-uiscene-migration.
  3. Install @capacitor/app (npm install @capacitor/app && npx cap sync).
  4. Register the JS handler once, at app bootstrap (before the router renders), using both appUrlOpen and getLaunchUrl(). See below.
  5. Configure platforms: custom scheme (Info.plist CFBundleURLTypes, Android <intent-filter>), then verified links per platform:
    • iOS Universal Links: references/ios-universal-links.md
    • Android App Links: references/android-app-links.md
  6. Route in the framework router: references/routing-patterns.md (React, Vue, Angular, OAuth callbacks, deferred links).
  7. Verify with the cold/warm matrix below on a real device.

Only load a reference when its topic is in play.

JS Handler (the part every app needs)

import { App, type URLOpenListenerEvent } from '@capacitor/app';

let lastHandled: string | undefined;

function route(rawUrl: string) {
  if (rawUrl === lastHandled) return; // cold start can deliver the same URL to both paths
  lastHandled = rawUrl;
  const url = new URL(rawUrl);
  // myapp://product/123 -> host "product", pathname "/123"
  // https://example.com/product/123 -> pathname "/product/123"
  const path = url.protocol.startsWith('http')
    ? url.pathname
    : `/${url.host}${url.pathname}`;
  router.navigate(path + url.search); // your router
}

export async function initDeepLinks() {
  await App.addListener('appUrlOpen', (event: URLOpenListenerEvent) => route(event.url));
  const launch = await App.getLaunchUrl(); // resolves undefined or { url }
  if (launch?.url) route(launch.url);
}

Traps:

  • Custom scheme URLs parse with the first segment as host, not pathname. Normalize both forms as above.
  • Register the listener at bootstrap, not inside a lazily loaded page. A listener added after the event fired never sees it; getLaunchUrl() is the fallback for cold start.
  • Wrap state updates from the listener in your framework's zone/run loop (Angular NgZone.run) or navigation silently does nothing.
  • Dedupe: on cold start the URL can be reachable via both getLaunchUrl() and appUrlOpen.
  • Validate and allow-list paths before navigating; a deep link is untrusted input.

Custom Scheme Config

iOS ios/App/App/Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.example.app</string>
    <key>CFBundleURLSchemes</key>
    <array><string>myapp</string></array>
  </dict>
</array>

Android AndroidManifest.xml, inside the existing MainActivity (keep the template's android:launchMode="singleTask" so warm opens reuse the activity):

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

Verification

Run every row on a physical device (Universal Links do not work reliably from the Simulator address bar; tap from Notes/Messages instead).

ScenarioiOSAndroid
Custom scheme, warmxcrun simctl openurl booted "myapp://product/123"adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123" com.example.app
Custom scheme, coldkill app, then same commandadb shell am force-stop com.example.app, then same command
Verified link, warmtap https://example.com/product/123 in Notesadb shell am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://example.com/product/123"
Verified link, coldswipe app away, tap linkforce-stop, then same command
Ownership checkcurl -sI https://example.com/.well-known/apple-app-site-association (200, no redirect)adb shell pm get-app-links com.example.app shows verified

Pass criteria: each case lands on the right route, appUrlOpen logs once, and on cold start getLaunchUrl() returns the URL. After a Capacitor 8.5 scene migration, re-run the full matrix: cold launch takes a different native path (scene(_:willConnectTo:options:)) than warm open (scene(_:openURLContexts:) / scene(_:continue:)).

Grep for dead handlers after migration:

grep -n "open url: URL\|continue userActivity" ios/App/App/AppDelegate.swift
grep -n "SceneDelegateProxy" ios/App/App/SceneDelegate.swift

Error Handling

Symptom / messageCauseFix
Link opens in Safari, app installedAASA not fetched, wrong appID, entitlement missing, or user long-pressed and chose "Open in Safari"Check AASA via Apple CDN, TEAMID.bundleId, applinks: entitlement; long-press link and choose "Open in App" to reset
Custom AppDelegate URL code stopped running after 8.5 updateScene manifest exists; iOS calls SceneDelegate insteadMove logic to SceneDelegate (reference: ios-scene-delegate)
Warm opens work, cold launch lands on homeListener registered late, or custom SceneDelegate code ignores connectionOptionsCall getLaunchUrl() at bootstrap; keep SceneDelegateProxy.shared.scene(_:willConnectTo:options:)
Safari cannot open the page because the address is invalidCustom scheme not registered in installed buildAdd CFBundleURLTypes, rebuild (not just cap sync)
Error: Activity not started, unable to resolve IntentNo intent-filter matches scheme/hostFix <data> entries; reinstall app
pm get-app-links shows 1024 or noneassetlinks.json unreachable, wrong package, or wrong SHA-256 (often the Play App Signing key)Fix file, then adb shell pm verify-app-links --re-verify com.example.app
Android shows app chooser for https linkDomain not verified (autoVerify missing or failed)Add android:autoVerify="true", re-verify
OAuth redirect returns to browser tab, not appRedirect URI does not match registered scheme / verified domainRegister exact redirect URI; prefer verified https link

Resources

FAQ

What does the capacitor-deep-linking skill do?

Implements and debugs deep links in Capacitor apps - custom URL schemes, iOS Universal Links (apple-app-site-association, Associated Domains entitlement), Android App Links (assetlinks.json, autoVerify), and routing via @capacitor/app appUrlOpen + App.getLaunchUrl(). Covers the Capacitor 8.5+ UIScene change where AppDelegate application(:open:options:) and continue userActivity stop being called and custom handling must move to SceneDelegate, plus cold vs warm launch testing. Use when links open Safari/Chrome instead of the app, appUrlOpen never fires, the launch URL is lost on cold start, or OAuth redirects do not return to the app. Do not use for the full UIScene migration (capacitor-uiscene-migration), push notification taps (capacitor-push-notifications), or social login plugin setup (capacitor-plugins).

How do I install capacitor-deep-linking?

Run npx skills add https://github.com/cap-go/capgo-skills --skill capacitor-deep-linking in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/capacitor-deep-linking/ (personal) or .claude/skills/capacitor-deep-linking/ (project).

Which AI agents can use capacitor-deep-linking?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is capacitor-deep-linking safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains capacitor-deep-linking?

It is published by cap-go in cap-go/capgo-skills. It has 1,277 recorded installs.

More from cap-go/capgo-skills

View all →

capacitor-best-practices

cap-go/capgo-skills

Review and set up Capacitor apps against current best practices (Capacitor 8.5 stable, 9 alpha via @next). Use for a Capacitor codebase review, capacitor.config.ts audit, new project setup, aligning @capacitor/core/cli/ios/android and plugin majors, plugin patterns (availability checks, checkPermissions/requestPermissions, error code handling, web fallbacks), or release prep; or on " " plugin is not implemented on ios, Could not find the web assets directory, platform has not been added yet, a dev server.url shipped to production, lost edits in android/app/src/main/assets/public, SPM vs CocoaPods, or AppDelegate/UIScene drift. Do not use for security audits (capacitor-security), performance (capacitor-performance), tests (capacitor-testing), crashes (debugging-capacitor), major upgrades (capacitor-app-upgrades), SPM migration (cocoapods-to-spm), UIScene adoption (capacitor-uiscene-migration), or live updates (capgo-live-updates).

ionic-design

cap-go/capgo-skills

Builds and reviews Ionic Framework UI in Capacitor apps (Ionic 9 current, Ionic 8 still common) - page structure (ion-header/ion-content/ion-footer), lists, modern form syntax (label/labelPlacement on ion-input/ion-select), sheet modals, tabs + ion-router-outlet, CSS variable theming, dark palettes (palettes/dark.system.css, .ion-palette-dark), iOS vs MD mode, and Ionic 8 - 9 breaking changes (React Router 6, Vue Router 5, Angular 18-22 zoneless, removed ion-picker-legacy, boolean autocorrect, npx @ionic/migrate). Use when writing Ionic components, theming an Ionic app, fixing platform-mode styling, or upgrading @ionic/react, @ionic/vue, @ionic/angular. Do not use for Konsta UI (konsta-ui), Tailwind setup (tailwind-capacitor), raw safe-area CSS (safe-area-handling), keyboard resize (capacitor-keyboard), or Ionic Appflow/Enterprise migrations (ionic-appflow-migration, ionic-enterprise-sdk-migration).

Frontend & Design1.6K installs

capacitor-plugins

cap-go/capgo-skills

Pick, install, and wire up Capacitor plugins for native features (camera, filesystem, geolocation, biometrics, in-app purchases, social login, SQLite, calendar, contacts, health, BLE, NFC, etc.). Covers official @capacitor/ packages (Capacitor 8.5 stable, 9 alpha on next) plus the 145-package @capgo/ index, native permission keys (Info.plist, AndroidManifest, variables.gradle minSdk), npx cap sync / cap ls verification, and errors like '"Camera" plugin is not implemented on ios', 'unable to find plugin', UNIMPLEMENTED, and OS-PLUG- codes. Use for "which plugin should I use for X", adding a plugin, or a plugin call failing after install. Do not use for push setup (capacitor-push-notifications), splash assets (capacitor-splash-screen), keyboard layout (capacitor-keyboard), deep links (capacitor-deep-linking), OTA updates (capgo-live-updates), upgrading plugin source code (capacitor-plugin-upgrades), or adding SPM to a plugin (capacitor-plugin-spm-support).

Frontend & Design1.6K installs

debugging-capacitor

cap-go/capgo-skills

Diagnoses and fixes runtime bugs in Capacitor iOS/Android apps - white/blank screen on launch, startup crashes, "X" plugin is not implemented on ios/android, net::ERRCLEARTEXTNOTPERMITTED, App Transport Security blocks, CORS on capacitor://localhost, permission denials, live reload not connecting, and Capacitor 8.5+ UIScene regressions (appUrlOpen, universal links, pause/resume silent because AppDelegate callbacks stopped). Covers Safari Web Inspector, chrome://inspect, Xcode/LLDB, Android Studio, npx cap run --url (Capacitor 9) vs -l --host (8), xcrun simctl openurlprivacypush, xcrun devicectl device process launchopenURL, xctrace. Do not use for only reading device logs (ios-android-logs), the UIScene migration itself (capacitor-uiscene-migration), deep-link setup (capacitor-deep-linking), speed (capacitor-performance), crash SDKs (capacitor-native-observability), tests (capacitor-testing), or upgrades/build errors (capacitor-app-upgrades, cocoapods-to-spm).

capacitor-testing

cap-go/capgo-skills

Testing guide for Capacitor apps and plugins. Covers web-layer unit and component tests with Vitest and mocked Capacitor plugins (vi.mock('@capacitor/core'), registerPlugin), Playwright for the web build, Appium E2E with NATIVEAPP / WEBVIEW context switching, native plugin unit tests with Swift Testing vs XCTest (what can migrate, CAPPluginCall patterns, xcodebuild test on a simulator) and Android JUnit, plus an agent-driven verification loop on simulators and devices (build, install, launch, screenshot, UI hierarchy, tap, logs) with xcrun simctl, xcrun devicectl, adb, Xcode MCP device tools, and live reload via npx cap run --url (Capacitor 9) or -l --host --port (Capacitor 8). Use when adding tests, mocking plugins, "does it work on device", verifying a UI change on a simulator, migrating XCTest to Swift Testing, or setting up test CI. Do not use for crash log capture (ios-android-logs), WebView debugging sessions (debugging-capacitor), or build pipelines (capacitor

Testing & QA1.3K installs

safe-area-handling

cap-go/capgo-skills

Fixes notch, Dynamic Island, home indicator, display cutout, and system bar overlap in Capacitor apps. Covers viewport-fit=cover + env(safe-area-inset-), the Capacitor 8 SystemBars core plugin (insetsHandling, injected --safe-area-inset- CSS variables for Android WebView < 140, removal of android.adjustMarginsForEdgeToEdge), Android 15/16 enforced edge-to-edge (overlaysWebView and backgroundColor of @capacitor/status-bar no longer work at targetSdk 36), iOS contentInset, and insets that change at runtime on rotation, iPad/foldable resizable windows. Use when headers sit under the status bar, tab bars hide behind the gesture bar, env() returns 0 on Android, or layout broke after upgrading to Capacitor 8 / targetSdk 35+. Do not use for keyboard overlap (capacitor-keyboard), Ionic/Konsta component theming (ionic-design, konsta-ui), Tailwind utility setup (tailwind-capacitor), or iOS multi-window/resizability adoption (capacitor-ios-resizability).

Mobile1.3K installs

Popular code quality & workflow skills

View category →

grill-me

mattpocock/skills

A relentless interview to sharpen a plan or design.

Scan a codebase for deepening opportunities, present them as a visual HTML report, then grill through whichever one you pick.

handoff

mattpocock/skills

Compact the current conversation into a handoff document for another agent to pick up.

domain-modeling

mattpocock/skills

Build and sharpen a project's domain model. Use when discussing codebase terminology, writing or editing a GLOSSARY.md, or recording or editing an ADR.

codebase-design

mattpocock/skills

Shared vocabulary for designing deep modules. Use when the user wants to design or improve a module's interface, find deepening opportunities, decide where a seam goes, make code more testable or AI-navigable, or when another skill needs the deep-module vocabulary.

diagnosing-bugs

mattpocock/skills

Diagnosis loop for hard bugs and performance regressions. Use when the user says "diagnose"/"debug this", or reports something broken/throwing/failing/slow.