Deep Links

Universal Links vs App Links: iOS and Android Comparison

Universal Links vs App Links: iOS and Android Comparison

Picture this. It's a Tuesday afternoon product meeting, the kind with too many people on the call and a shared screen nobody can quite read. Someone from marketing wants links in email campaigns to open the app directly. An engineer says "we already support deep links." A product manager asks what happens on iOS versus Android. Long pause. Then three people start talking at once, each describing a slightly different version of reality. One person says "universal links." Another says "app links." A third just keeps saying "intent filters." Nobody agrees on how links should work on mobile, and by the end of the meeting, the only consensus is that someone needs to "look into it" and report back.

Key Takeaways

  • The Basic Idea Behind Both Systems
  • Universal Links on iOS: The Apple Way
  • Android App Links: The Google Way
  • The Differences That Actually Matter in Practice
  • When Your Team Needs to Choose (or Not)

Been that someone — or about to be? Good, because this is for you. I want to walk through how Apple's Universal Links and Google's Android App Links actually work, where they overlap, where they diverge, and what the practical differences mean for your team. Translating between developer-speak and product-speak as we go, because half the confusion in that meeting probably came from people using the same words to mean different things.

The Basic Idea Behind Both Systems

Before digging into the differences, let's establish what these two systems share. Universal Links (iOS) and App Links (Android) are trying to solve the same problem: when a user taps a link — in an email, a text message, a social media post, a web page — the operating system should be smart enough to open that link directly in the relevant app if it's installed, and fall back to the web browser if it isn't. No weird redirect pages. No "open in app?" dialogs. Just a smooth transition from tapping a link to being inside the right app at the right content.

That's the promise, anyway. Execution differs quite a bit between the two platforms, and the devil's hiding in about forty different implementation details. Identical aspiration, though. Apple and Google both looked at the messy world of custom URL schemes (things like myapp://product/123) and said, essentially, "we can do better." Custom URL schemes had real problems — they weren't unique, they couldn't fall back to a browser gracefully, and malicious apps could hijack them. Universal Links and App Links both use standard HTTPS URLs instead, which means the link is a real web address that also happens to trigger app-opening behavior when the app is installed.

Here's a high-level comparison before we dig in:

FeatureUniversal Links (iOS)App Links (Android)
IntroducediOS 9 (2015)Android 6.0 / API 23 (2015)
URL typeStandard HTTPSStandard HTTPS
Verification fileapple-app-site-association (AASA)assetlinks.json
File location/.well-known/apple-app-site-association/.well-known/assetlinks.json
File formatJSON (no .json extension)JSON
Requires HTTPSYesYes
User prompt to confirmNo (silent open)No (when verified)
Fallback behaviorOpens in SafariOpens in default browser
Can user disable?Yes (long-press, open in Safari)Yes (app settings)

Reading that table, it looks like they're basically the same thing with different file names. They're not. Let me explain why. Relevant reading: App Deep Linking: A Developer's Guide.

Universal Links on iOS: The Apple Way

Universal Links vs App Links: iOS and Android Comparison
Universal Links vs App Links: iOS and Android Comparison

Apple, as Apple tends to do, designed Universal Links with a very specific philosophy: security first, user control second, developer convenience somewhere further down the list. When it works, the system works well. Getting there can be... an experience.

Here's how setup works on the iOS side. Two things need to happen. Your web server hosts an Apple App Site Association file. Your app declares which domains it associates with. With both correctly configured, iOS checks the association when the app is installed (not at link-click time — important distinction) and registers that your app can handle URLs from those domains.

An AASA file looks something like this:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAMID.com.yourcompany.yourapp"],
        "components": [
          {
            "/": "/products/*",
            "comment": "Match all product pages"
          },
          {
            "/": "/blog/*",
            "comment": "Match all blog pages"
          },
          {
            "/": "/account/*",
            "exclude": true,
            "comment": "Don't open account pages in app"
          }
        ]
      }
    ]
  }
}

A few things to notice. Combining your Apple Developer Team ID with your app's bundle identifier produces the appIDs field. Getting either wrong means the whole thing silently fails. No error message. No console warning. Just... doesn't work. URL path control comes through the components array (which replaced the older paths array in iOS 13+), letting you specify which paths should open in your app and which should be excluded. Quite useful, that exclusion ability — maybe product pages should open in the app but your privacy policy page should always stay in the browser.

Xcode configuration requires adding the Associated Domains capability and listing your domains with the applinks: prefix:

applinks:www.yourdomain.com
applinks:yourdomain.com

Notice both the www and non-www versions are listed. Forgetting one when some of your links use it means those links won't open in the app. I've watched teams spend hours debugging this only to realize they missed the www variant. Obvious in hindsight. Catches people regularly anyway.

Now here's where Apple's approach gets interesting — and sometimes frustrating. iOS downloads and caches the AASA file when the app is installed or updated. Not when the user clicks a link. So updating your AASA file on your server won't affect existing app installations until the app is updated or reinstalled. During development, this caching behavior can make you question your sanity. Change the file, tap the link, nothing changes. Change it again. Still nothing. Wondering if you've misunderstood something fundamental? Probably not — you just need to delete the app and reinstall it, or wait for the system to refresh its cache (which happens on its own schedule, not yours).

A developer mode for AASA arrived in iOS 14+, bypassing some caching through the ?mode=developer flag in the associated domain entry. Helpful during development, but not something you'd ship to production. New to all of this? What Are Deep Links and Why They Matter breaks it down step by step.

One behavior that trips up developers and product people alike: when a user is in Safari and taps a Universal Link to a domain they're already on, iOS will NOT open the app. Same-domain navigation stays in Safari. Makes sense from a user-experience philosophy standpoint — Apple doesn't want browsing a website to suddenly yank you into an app without warning. Your "open in app" banner strategy needs to account for this, though. Cross-domain links work fine. Links from other apps work fine. Same-domain links in Safari? Stay in Safari.

Then there's the long-press escape hatch. Long-pressing a Universal Link gives the option to "Open in Safari" instead of the app. Here's the really tricky part: choosing that option causes iOS to remember their preference for that domain. Future taps on links from that domain will open in Safari, not the app. Essentially, the user "breaks" Universal Links for your domain, and the only way to re-enable it is for the user to long-press again and choose "Open in [App Name]." Your support team will field tickets about this. Guaranteed.

Android App Links: The Google Way

Android's approach shares the same DNA but implementation details reflect Google's more open philosophy. More options, more configuration possibilities, and — depending on your perspective — either more flexibility or more rope to hang yourself with.

First, some terminology cleanup. A hierarchy of link types on Android confuses people constantly:

Link TypeWhat It DoesVerification Required?User Prompt?
Deep LinksAny URI that takes users to app contentNoMaybe (disambiguation dialog)
Web LinksDeep links using http/https schemesNoMaybe (disambiguation dialog)
App LinksVerified web links with autoVerifyYesNo (opens directly)

When product people say "deep links on Android," they usually mean App Links — the verified kind that open without a dialog. Developers might mean any of the three, though. More confusion stems from this terminology mismatch than from any technical detail. A product spec that says "implement deep links" without specifying which kind will get you the wrong thing about half the time.

Proper App Links (the kind equivalent to Universal Links) require the assetlinks.json file on your server:

[
  {
    "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"
      ]
    }
  }
]

That sha256_cert_fingerprints field contains the SHA-256 fingerprint of your app's signing certificate. Here's where Android gets more complex than iOS: different signing certificates might exist for your debug build, your release build, and with Google Play App Signing (which you probably should be using), the upload certificate and the app signing certificate differ. Each context needs the right fingerprint. Development requires your debug keystore fingerprint. Production needs the one from Google Play Console. Getting this wrong is the number one reason App Links verification fails, and I'm not even slightly exaggerating. Related reading: Deep Linking for Better Mobile User Experience.

Grab your debug certificate fingerprint like this:

keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android

For your release certificate through Play Console, go to Setup > App Integrity > App signing key certificate.

In your app's AndroidManifest.xml, declare intent filters with the autoVerify attribute:

<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="https"
      android:host="www.yourdomain.com"
      android:pathPattern="/products/.*" />
  </intent-filter>
</activity>

Adding android:autoVerify="true" is what makes this an App Link rather than just a regular deep link. At install time, Android fetches the assetlinks.json file from the declared domain and verifies that the package name and certificate fingerprint match. Successful verification means tapping matching URLs will open your app directly. Failed verification triggers a fallback to the disambiguation dialog — that "Open with..." popup where the user chooses between your app and the browser.

Verification happens at install time, similar to iOS. Re-checking tends to be faster on Android, though. And starting with Android 12, Google made significant changes to how App Links behave. Unverified links that previously showed the disambiguation dialog now just open in the browser by default. Huge deal. Apps that had been getting away with sloppy verification setups suddenly stopped opening from links. Supporting Android 12+ means getting verification right isn't optional — it's the only path to app-opening behavior.

Something Android gives you that iOS doesn't: command-line verification testing. Running this shows you the verification status for each domain your app claims:

adb shell pm get-app-links com.yourcompany.yourapp

Manual verification can also be triggered with:

adb shell pm verify-app-links --re-verify com.yourcompany.yourapp

Genuinely helpful, these tools. Over on iOS, if verification isn't working, your debugging options are... limited. Apple's AASA validator tool and Console.app can show system logs, but it's nowhere near as straightforward as running an adb command and getting a status back. Worth reading next: Understanding Affiliate Links: A Beginner Guide.

The Differences That Actually Matter in Practice

I've given Android a bit more detail above because, honestly, there are more moving parts and more things that can go wrong. Let's pull back and look at the differences that matter when you're sitting in that product meeting trying to make decisions.

ConsiderationUniversal Links (iOS)App Links (Android)
Path matchingPattern matching with wildcards, exclusionspathPattern, pathPrefix, or exact path in manifest
Multiple apps per domainSupported (list multiple appIDs)Supported (list multiple targets in assetlinks.json)
SubdomainsEach subdomain needs its own AASA file (or use wildcard in applinks entitlement)Each subdomain needs its own assetlinks.json
DebuggingConsole.app logs, Apple AASA validator, swcd diagnosticsadb commands, Digital Asset Links API tester
CDN/redirect issuesAASA must be served directly (no redirects)assetlinks.json must be served directly (no redirects)
Content-Type headerapplication/json recommendedapplication/json required
File size limit~128 KBNo documented limit (keep it reasonable)
Same-domain limitationLinks within Safari on same domain don't triggerNo equivalent limitation

Probably the most impactful behavioral difference in everyday use sits in that last row. Browsing your website in Chrome on Android and tapping a link to another page on your site? App can still open. Same scenario on iOS stays in Safari. Your iOS "smart app banner" strategy becomes more important because that might be the only way users get from your mobile website into your app during a same-domain browsing session.

Another practical difference: how each platform handles the scenario where the app isn't installed. With Universal Links, iOS just loads the URL in Safari. Clean and simple. Android's fallback is also the browser, but additional options exist. Setting up a fallback URL in your link or directing uninstalled users to the Play Store are both possible. Some teams use a combination — a web page that detects the platform and either shows the content or prompts an install. Nice flexibility, but it means more decisions to make and more code paths to test.

Worth touching on something that bites cross-platform teams regularly: the timing of when each OS checks the verification file. Install-time verification is the standard for both, but subtle differences in retry behavior and cache duration exist. iOS, as I mentioned, can be stubborn about caching. Android tends to re-verify more frequently, and the adb re-verify command gives you an escape hatch during development. Rolling out a change to your link configuration? Expect it to propagate to Android users faster than iOS users. Plan your rollout accordingly. Maybe don't send that marketing email campaign the same day you update your AASA file.

For product managers reading this and translating it into requirements: what's critical to communicate to your team is that both platforms require server-side configuration (the JSON files) AND client-side configuration (the app code), and both sides need to be deployed and in agreement with each other. A mismatch between what the server says and what the app declares will cause silent failures. Unlike a feature flag you can toggle server-side, it's a handshake, and both hands need to be present.

Something I don't see discussed enough is what happens when things go wrong in production. Let's say your ops team migrates your website to a new CDN and the CDN introduces a redirect on the /.well-known/ path. Both platforms require the verification file to be served directly — no 301s, no 302s, no redirect chains. Your links will silently stop working for new installs. Existing installs might continue working from cached verification data, which makes the bug even harder to notice. Set up monitoring on those verification endpoints. Seriously. A simple uptime check that verifies the file is being served with a 200 status code and the correct Content-Type header could save you a very bad week.

When Your Team Needs to Choose (or Not)

Choosing probably isn't necessary. Building a mobile app that exists on both platforms means you need both Universal Links and App Links. They're not competing standards — they're each platform's version of the same concept. What you're really deciding is implementation priority and depth. For the full picture, read 301 vs 302 Redirects: Impact on Link Equity.

Some teams go deep on iOS first because their user base skews that way. Others prioritize Android because the debugging tools are better and they want to get the pattern right before tackling the more opaque iOS implementation. No universally correct order exists, but I'd suggest starting with whichever platform you have more engineering experience on. Getting one working end-to-end, understanding the gotchas, and then applying those lessons to the other platform is more efficient than trying to do both simultaneously and debugging two broken systems at once.

Server-side setup can be shared, though. Your /.well-known/ directory will contain both files, and many teams generate them from a single source of truth — a configuration file or a build script that produces both apple-app-site-association and assetlinks.json from the same domain and path definitions. I'd encourage this approach. Reducing the chance of the two files drifting out of sync, it also makes the link configuration reviewable in pull requests like any other code change.

One more thing before I wrap up. I've lost count of the number of times I've seen teams test their link configuration exclusively in simulators and emulators, ship to production, and then discover it doesn't work on real devices. Simulators can behave differently around network requests, certificate validation, and caching. Particularly, the iOS Simulator has historically had quirks with Universal Links that don't exist on physical devices. Some versions of the Simulator don't even attempt AASA validation. Somewhat better is the Android Emulator, but still not a perfect replica of how a real device handles App Links verification.

So: test on real devices. Simulators lie.

Simran Sinha
Written by

Simran Sinha

SEO specialist and content strategist with over 8 years of experience in digital marketing and link building.

Comments (0)

No comments yet. Be the first to share your thoughts!

Leave a Comment

Your email will not be published.

Related Articles