Back to all articles

Deep Linking for Mobile Apps: Complete Implementation Guide 2025

Apps with deep linking see 2x higher user engagement and 3x better conversion from campaigns. This guide covers everything from basic deep links to advanced attribution.

What is Deep Linking?

Types of Links

1. Deep Links (URI Schemes):
   myapp://profile/123
   - Only works if app installed
   - Falls back to error if not installed

2. Universal Links (iOS) / App Links (Android):
   https://myapp.com/profile/123
   - Opens app if installed
   - Opens website if not
   - Seamless experience

3. Deferred Deep Links:
   - User clicks link without app
   - App Store/Play Store opens
   - After install, opens to specific content
   - Requires third-party SDK

Use Cases

  • Marketing campaigns (email, social, ads)
  • Referral programs (invite friends)
  • Content sharing (share specific items)
  • Password reset links
  • Email verification
  • Push notification actions
  • QR codes

iOS Universal Links

Setup Steps

1. Enable Associated Domains:

Xcode:
1. Select target → Signing & Capabilities
2. Add "Associated Domains"
3. Add domains:
   - applinks:myapp.com
   - applinks:www.myapp.com

2. Create apple-app-site-association file:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.myapp.bundle",
        "paths": [
          "/profile/*",
          "/products/*",
          "/invite/*",
          "NOT /admin/*"
        ]
      }
    ]
  }
}

Host at: https://myapp.com/.well-known/apple-app-site-association
Requirements:
- HTTPS only
- No .json extension
- Content-Type: application/json
- Max 128 KB

3. Handle Universal Links in App:

// AppDelegate.swift
func application(_ application: UIApplication,
  continue userActivity: NSUserActivity,
  restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {

  guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
        let url = userActivity.webpageURL else {
    return false
  }

  // Parse URL
  handleDeepLink(url)
  return true
}

// SwiftUI App
.onOpenURL { url in
  handleDeepLink(url)
}

func handleDeepLink(_ url: URL) {
  // Parse URL components
  let pathComponents = url.pathComponents

  // Example: https://myapp.com/profile/123
  if pathComponents.count >= 3 && pathComponents[1] == "profile" {
    let userId = pathComponents[2]
    navigateToProfile(userId: userId)
  }
  else if pathComponents.count >= 3 && pathComponents[1] == "products" {
    let productId = pathComponents[2]
    navigateToProduct(productId: productId)
  }
}

Testing Universal Links

Testing methods:

1. Notes app:
   - Type link in Notes
   - Long press → Open in App

2. Safari:
   - Type link in Safari
   - Should redirect to app

3. Terminal:
   xcrun simctl openurl booted "https://myapp.com/profile/123"

4. Validate file:
   https://branch.io/resources/aasa-validator/
   https://search.developer.apple.com/appsearch-validation-tool/

Common issues:
- HTTPS not working → Check certificate
- Not redirecting → Check AASA file hosting
- Wrong format → Validate JSON

Android App Links

Setup Steps

1. Add Intent Filters (AndroidManifest.xml):


  
    
    
    

    
    
  

2. Create assetlinks.json:

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.myapp.android",
    "sha256_cert_fingerprints": [
      "14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
    ]
  }
}]

Host at: https://myapp.com/.well-known/assetlinks.json

Get SHA256 fingerprint:
keytool -list -v -keystore my-release-key.keystore

3. Handle App Links in Activity:

class MainActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleDeepLink(intent)
  }

  override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    handleDeepLink(intent)
  }

  private fun handleDeepLink(intent: Intent?) {
    val appLinkAction = intent?.action
    val appLinkData = intent?.data

    if (Intent.ACTION_VIEW == appLinkAction) {
      appLinkData?.let { url ->
        // Parse URL
        when {
          url.pathSegments.firstOrNull() == "profile" -> {
            val userId = url.pathSegments.getOrNull(1)
            navigateToProfile(userId)
          }
          url.pathSegments.firstOrNull() == "products" -> {
            val productId = url.pathSegments.getOrNull(1)
            navigateToProduct(productId)
          }
        }
      }
    }
  }
}

Testing App Links

ADB command:
adb shell am start -W -a android.intent.action.VIEW -d "https://myapp.com/profile/123" com.myapp.android

Verify settings:
adb shell pm get-app-links com.myapp.android

Test in browser:
- Open Chrome
- Type link
- Should show "Open in app" dialog

Validate assetlinks.json:
https://digitalassetlinks.googleapis.com/v1/statements:list?source.web.site=https://myapp.com

URI Schemes (Custom Schemes)

iOS Setup

Info.plist:
CFBundleURLTypes

  
    CFBundleURLSchemes
    
      myapp
    
    CFBundleURLName
    com.myapp.url
  


Handle in AppDelegate:
func application(_ app: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {

  // Parse: myapp://profile/123
  handleDeepLink(url)
  return true
}

Android Setup

AndroidManifest.xml:

  
  
  

  


Same handling code as App Links above.

Testing URI Schemes

iOS:
xcrun simctl openurl booted "myapp://profile/123"

Android:
adb shell am start -W -a android.intent.action.VIEW -d "myapp://profile/123"

Web:
Open in App

Deferred Deep Linking

What is Deferred Deep Linking?

Flow:
1. User clicks link (app not installed)
2. Redirects to App Store/Play Store
3. User installs app
4. First app open → navigate to intended content

Technical challenge:
- How to pass data through install?

Solution:
- Device fingerprinting
- Or referrer tracking (Android)
- Third-party SDKs handle this

Branch.io Implementation

iOS Setup:

// Install SDK
pod 'Branch'

// AppDelegate.swift
import Branch

func application(_ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions:
  [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  Branch.getInstance().initSession(launchOptions: launchOptions) { params, error in
    guard let data = params as? [String: AnyObject] else { return }

    // Check if opened from link
    if let deepLinkPath = data["$deeplink_path"] as? String {
      self.handleDeepLink(path: deepLinkPath, data: data)
    }
  }

  return true
}

// Create deep link
func createBranchLink(productId: String) {
  let buo = BranchUniversalObject(canonicalIdentifier: "product/\(productId)")
  buo.title = "Product Name"
  buo.contentDescription = "Check out this product!"
  buo.imageUrl = "https://example.com/image.jpg"
  buo.publiclyIndex = true
  buo.locallyIndex = true
  buo.contentMetadata.customMetadata["product_id"] = productId

  let lp = BranchLinkProperties()
  lp.feature = "sharing"
  lp.channel = "social"

  buo.getShortUrl(with: lp) { url, error in
    if let url = url {
      // Share URL: https://myapp.app.link/abc123
      self.shareLink(url)
    }
  }
}

Android Setup:

// build.gradle
dependencies {
  implementation 'io.branch.sdk.android:library:5.+'
}

// AndroidManifest.xml

  


// Application class
class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    Branch.getAutoInstance(this)
  }
}

// MainActivity
class MainActivity : AppCompatActivity() {
  override fun onStart() {
    super.onStart()
    Branch.sessionBuilder(this).withCallback { referringParams, error ->
      if (referringParams != null) {
        val productId = referringParams.optString("product_id")
        if (productId.isNotEmpty()) {
          navigateToProduct(productId)
        }
      }
    }.withData(intent?.data).init()
  }

  override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    setIntent(intent)
    Branch.sessionBuilder(this).reInit()
  }
}

Attribution Tracking

Track Campaign Performance

URL parameters:
https://myapp.com/invite/abc123?
  utm_source=instagram
  &utm_medium=story
  &utm_campaign=launch_week
  &utm_content=variant_a

Track in app:
func handleDeepLink(_ url: URL) {
  let components = URLComponents(url: url, resolvingAgainstBaseURL: true)
  let queryItems = components?.queryItems

  let source = queryItems?.first(where: { $0.name == "utm_source" })?.value
  let medium = queryItems?.first(where: { $0.name == "utm_medium" })?.value
  let campaign = queryItems?.first(where: { $0.name == "utm_campaign" })?.value

  Analytics.logEvent("deep_link_open", parameters: [
    "source": source ?? "unknown",
    "medium": medium ?? "unknown",
    "campaign": campaign ?? "unknown"
  ])

  // Navigate to content
  handleNavigation(url)
}

Best Practices

URL Structure

Good:
✓ https://myapp.com/products/123
✓ https://myapp.com/invite/abc123
✓ https://myapp.com/reset-password?token=xyz

Bad:
❌ https://myapp.com?screen=products&id=123 (unclear)
❌ https://myapp.com/p/123 (not descriptive)
❌ myapp://open?params=base64encodedmess (ugly)

Fallback Handling

Always have fallbacks:
- Link doesn't match → Home screen
- Content deleted → Show error + similar content
- User not authorized → Login screen
- Network error → Cache or retry

func handleDeepLink(_ url: URL) {
  guard let content = parseURL(url) else {
    // Fallback to home
    navigateToHome()
    return
  }

  fetchContent(content.id) { result in
    switch result {
    case .success(let data):
      navigateToContent(data)
    case .failure(let error):
      if error == .notFound {
        showNotFoundError()
        navigateToHome()
      } else {
        showRetryDialog()
      }
    }
  }
}

Security Considerations

  • Validate all URL parameters
  • Don't trust user input from links
  • Verify authentication before sensitive actions
  • Use HTTPS for web-based deep links
  • Rate-limit link generation

Analytics

Key Metrics

Track:
- Deep link opens
- Install attribution (which link drove install)
- Conversion from deep link
- Time to conversion
- Deep link click-through rate
- Platform breakdown (iOS vs Android)

Events:
analytics.logEvent("deep_link_clicked", ...)
analytics.logEvent("deep_link_opened", ...)
analytics.logEvent("deep_link_converted", ...)

Conclusion

Deep linking dramatically improves user experience and campaign effectiveness. Implement universal/app links for seamless web-to-app transitions, add deferred deep linking for attribution, and track everything for optimization.

Need a Support URL for Your App?

Generate a compliant, professional support page in under a minute. Our easy-to-use generator creates everything you need for App Store and Google Play submissions.