Mobile Development5 min read

Deep Linking & Universal Links

Implement deep links and universal links for iOS and Android: routing incoming URLs to the right screen, deferred deep linking, and attribution tracking.

deep-linkinguniversal-linksiosandroid

Deep linking enables URLs to open specific screens in your app. Universal Links (iOS) and App Links (Android) open apps directly from HTTPS URLs - no custom URL scheme required. Deferred deep linking works even when the app isn't yet installed.

Prefer Universal Links/App Links over custom URL schemes - they're more secure and provide better UX. Always have a web fallback for the deep link URL. Test deferred deep linking from App Store ads. Centralize route handling in one router.

Workflow

  1. Set up Universal Links (iOS)Universal Links use HTTPS - more secure than custom schemes (attackers can't register your scheme on a user's device).
  2. Handle incoming URLs in the appDeep links can arrive in multiple ways (app open, background, first launch) - centralize handling.
  3. Implement Android App LinksAndroid App Links are the equivalent of Universal Links - verified HTTP URLs that open your app without ambiguity.
  4. Implement deferred deep linkingUsers clicking an ad before installing the app lose the context; deferred deep linking restores it after install.
  5. Test deep links thoroughlyDeep links interact with app state (logged in/out, onboarding complete/not) - edge cases cause crashes.

Output Format

AASA/assetlinks.json hosted, Universal Link + App Link handling in app, centralized router, deferred deep linking via SDK, web fallback pages.

Examples

AASA file: `{ "applinks": { "details": [{ "appIDs": ["ABCDEF1234.com.example.app"], "components": [{ "/": "/products/*", "comment": "Product detail pages" }] }] } }`
Deep link router: `func handle(_ url: URL) { switch url.path { case let p where p.hasPrefix("/products/"): let id = String(p.dropFirst("/products/".count)); navigate(to: .productDetail(id)); case "/profile": navigate(to: .profile); default: navigate(to: .home) } }`
Branch init: `Branch.getInstance().initSession(launchOptions: launchOptions) { params, _ in if let url = params?["~referring_link"] as? String { DeepLinkRouter.shared.handle(URL(string: url)!) } }`

Edge Cases & Gotchas

  • AASA caching: Apple CDN caches AASA for up to a week; changes take time to propagate. Use `apple-app-site-association` content-type `application/json`
  • App not installed flow: universal links fall through to Safari (your web page) if app isn't installed; ensure the web page has a smart app banner linking to the App Store
  • Multiple app variants: debug/release builds may have different bundle IDs; add all variants to the AASA file, or use a single bundle ID across build configurations

When to Break These Rules

  • Custom URL schemes (myapp://) are acceptable for inter-app communication between your own apps or known integrations where you control both sides.

Reference Files

  • Universal Links: https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app
  • App Links: https://developer.android.com/training/app-links
  • Branch.io: https://help.branch.io/
  • AASA validator: https://branch.io/resources/aasa-validator/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.