Mobile Deep Linking

Production engineering guide for mobile deep linking covering patterns, implementation strategies, and operational best practices.

Mobile deep linking is the practice of routing users directly to specific content or actions within a mobile app via a URL, enabling seamless transitions from web, email, SMS, or other apps. It matters when a user taps a link and expects to land precisely where they were directed—whether opening a product detail, starting a checkout flow, or navigating to a user's profile—without needing to manually browse through the app.

Directing Users to App Content

To route a user directly into an app, the system must resolve a URL to a specific screen or state. The core mechanism is a URL scheme registered in the mobile OS. For example, a link shop://product/123 triggers the app if installed, or falls back to a web page if not.

shop://product/123?category=electronics&ref=fb-ads

The app must register the scheme in its manifest. In Android, this is done in AndroidManifest.xml:

<activity android:name=".MainActivity">
  <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="shop" android:host="product" />
  </intent-filter>
</activity>

On iOS, the Info.plist file includes:

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

The autoVerify="true" in Android enables app indexing and verification via the Google App Indexing API. When the app is installed, the OS checks for a digital-assets.json file at https://example.com/.well-known/digital-assets.json, which lists the app’s deep links and their associated metadata.

Handling App Launch and Background State

When a user taps a deep link, the app may be in the foreground, background, or not running. The system must pass the URL to the app and restore the correct state.

On Android, onNewIntent() is called when the app is already running. If the app is not running, onCreate() is called with the intent data.

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

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

  private fun handleIntent(intent: Intent) {
    val uri = intent.data
    when (uri?.host) {
      "product" -> {
        val productId = uri.pathSegments[1]
        // Navigate to ProductDetailFragment with productId
      }
      "profile" -> {
        // Navigate to UserProfileFragment
      }
      else -> {
        // Handle unknown deep links
      }
    }
  }
}

On iOS, application(_:continue:restorationHandler:) in AppDelegate.swift receives the link:

func application(_ app: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]) -> Void) -> Bool {
  guard let uri = userActivity.webpageURL else { return false }

  if uri.scheme == "shop" {
    let path = uri.pathComponents
    if path.count >= 2, path[1] == "product" {
      let productId = path[2]
      // Navigate to ProductViewController with productId
    }
  }

  return true
}

The userActivity object carries metadata such as the title, icon, and context, allowing the OS to display a preview of the destination before launching.

Universal links provide a seamless experience by merging web URLs with app-specific deep links. When a user taps https://shop.example.com/product/123, the system checks for a apple-app-site-association file at https://shop.example.com/.well-known/apple-app-site-association.

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "ABCD1234EF.com.shop.app",
        "paths": ["/product/*", "/cart", "/profile"]
      }
    ]
  }
}

This file is served with Content-Type: application/json and can be cached. The app must declare its appID (Team ID + Bundle ID) and define paths for which URLs should open in the app. The OS checks for this file on the domain and, if found, uses it to route requests.

If the app is installed, the system opens the app directly; if not, it downloads the app and opens it with the link. The paths support wildcards (*) and exact matches.

An optional default-appears-in key can define which app icons and titles should appear in the OS’s app switcher and spotlight.

When a deep link fails, users are left confused. Common failure modes include:

  https://shop.example.com/product/123 → https://shop.example.com/product/123?fallback=true

The web version checks request.queryParams.fallback and provides a “Install app” button.

The app must decode the URL and validate that required parameters are present.

The solution is to defer UI updates until the app is fully ready. On Android, use PendingIntent to handle multiple link arrivals.

  Intent intent = new Intent(Intent.ACTION_VIEW, uri);
  intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
  startActivity(intent);

For scenarios like push notifications or scheduled emails, the app must handle links that arrive when the app is not running.

Push notifications from Firebase or Apple Push Notification Service (APNs) can carry a data payload that includes a deepLink key:

{
  "to": "fcm-token-123",
  "data": {
    "title": "New product available",
    "body": "Check out the latest smartphone.",
    "deepLink": "shop://product/123?ref=push"
  }
}

When the app is launched from a notification, the deepLink is extracted and processed as the initial intent. The app must handle both onCreate() and onNewIntent() paths.

For deferred deep linking—where a user taps a link but the app is not installed—the system stores the link and, after installation, delivers it to the app.

The process involves:

  1. User taps a link: https://shop.example.com/product/123.
  2. App not installed → redirect to https://shop.example.com/install?deepLink=shop%3A%2F%2Fproduct%2F123.
  3. User downloads and installs the app.
  4. App launches with the deep link, stored in Intent extras.

The app can enrich this flow by sending a referral_id or campaign_id to the backend, enabling attribution.

To ensure reliability, developers must verify deep link resolution across devices and networks.

Use curl to test universal link resolution:

curl -I https://shop.example.com/product/123

Check for 200 OK, Content-Type: application/json, and presence of apple-app-site-association.

Verify the file is served from the correct path:

curl -s https://shop.example.com/.well-known/apple-app-site-association | jq .

Test Android app linking with the adb tool:

adb shell am start -W -a android.intent.action.VIEW -d "shop://product/123" com.shop.app

This command starts the app with the given URI and reports the time taken and the activity that received the intent.

On iOS, use the xcrun command-line tool to verify universal links:

xcrun simctl install booted /path/to/MyApp.app
xcrun simctl launch booted com.shop.app
xcrun simctl openurl booted "shop://product/123"

The openurl command simulates a tap on a deep link and logs the app’s behavior.

Common Pitfalls and Silent Failures

Testing Deep Linking in CI

Automate deep link validation in continuous integration:

- name: Test Deep Links
  run: |
    # Start emulator
    emulator -avd Pixel_4_API_30 -no-window -no-audio -no-boot-anim &

    # Wait for boot
    while ! adb shell getprop sys.boot_completed | grep -q "1"; do
      sleep 1
    done

    # Install app
    adb install app-debug.apk

    # Test URL resolution
    adb shell am start -W -a android.intent.action.VIEW -d "shop://product/123" com.shop.app

    # Verify activity
    adb shell dumpsys activity | grep -A 5 -B 5 "com.shop.app/.MainActivity"

    # Check logs
    adb logcat -s "DeepLinkTest" --pid=$(adb shell pidof com.shop.app) | tail -n 10

This pipeline verifies that the app starts, receives the intent, and navigates to the correct screen.

Mobile deep linking is not a single feature but a coordinated pipeline across the OS, network, app, and backend. It requires precise handling of URLs, state, and timing. When it works, users feel the app is built around them. When it fails, they are lost in a maze of links and apps. The difference lies in the details.

This page was rewritten on 17 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.