Universal Links vs. Deep Links: When and how to use each

Posted on:
| Mobile Development
Picture

Contents

Introduction

Deep Links and Apple's Universal Links both send users directly into specific content inside an app, but they work quite differently under the hood.

It's important to note that Universal Links are Apple-specific. If you want to learn more about the Android counterpart, App Links, check out this other post I wrote.

A Deep Link is a URL that, when clicked, opens an app to a specific screen or functionality, bypassing the need to navigate through the app's main screens. In iOS, Deep Links are often implemented using custom URL schemes, like myapp://user/999, where myapp is a custom scheme defined in the app. If the app is installed and registered with this scheme, iOS will open the app directly when this URL is triggered. If it's not installed, the OS simply can't resolve that URI, so nothing happens (or you'll see an error page/dialog such as "Cannot Open Page" on iOS).

Configuration

To configure Deep Links on iOS, we need to:

  1. Declare a URL Scheme in Info.plist

    1. Open the project's Info.plist.
    2. Add a new URL types entry (CFBundleURLTypes) as an array.
    3. Inside it, add a dictionary with:
      • Identifier (CFBundleURLName): e.g. com.mycompany.myapp
      • URL Schemes (CFBundleURLSchemes): an array containing the custom scheme, e.g. myapp
    <key>CFBundleURLTypes</key>
    <array>
    	<dict>
    		<key>CFBundleURLName</key>
    		<string>com.mycompany.myapp</string>
    		<key>CFBundleURLSchemes</key>
    		<array>
    			<string>myapp</string>
    		</array>
    	</dict>
    </array>
  2. Implement URL handling in the app

    • Pre-iOS 13 (AppDelegate)

      func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
      	// Parse `url.scheme`, `url.host`, `url.path` or `url.queryItems`
      	// Route to the appropriate view controller
      	return true
      }
    • iOS 13+ (SceneDelegate)

      func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
      	guard let context = URLContexts.first else { return }
      	let url = context.url
      	// Handle the URL just like in AppDelegate
      }
  3. Parse and route

    Break down the URL into components:

    let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    let host = components?.host      // e.g. "user"
    let id   = components?.queryItems?.first(where: { $0.name == "id" })?.value

    Then, instantiate or navigate to the appropriate view controller based on those values.

  4. Test the Deep Links

    Type the URL into Safari on a device:

    myapp://profile/john_doe
    

    Or run the following line in the terminal of the simulator (or device):

    xcrun simctl openurl booted "myapp://user?id=999"
  5. Handle edge cases

    • App not installed: Tapping a custom‑scheme link does nothing, so consider pairing with a web‑based redirect page for a fallback.
    • Multiple schemes: We can register more than one under CFBundleURLSchemes if the app needs to handle several entry points.

With these steps, the app will recognize and respond to myapp://… URLs.

Fallback strategies

To handle errors gracefully in production, we can implement any of the following fallback strategies:

  • Smart redirect pages

    We can implement this doing the following:

    1. Link users to an HTTP(S) URL first (e.g. https://mywebsite.com/open?link=myapp://foo).
    2. On that page, run a brief script that attempts to open the app via the custom scheme.
    3. After a short timeout (e.g. 1 – 2 seconds), if the app hasn't opened, automatically redirect to the App Store or Play Store listing.
  • Universal Links instead

    By using Universal Links (iOS), taps on an HTTPS URL will either launch the app (if installed) or seamlessly load your website. No wasted taps and no manual fallback logic needed.

  • "Open in app" banners

    Some sites detect mobile browsers and show a banner ("Open this content in our app for a better experience"), with a direct link to the store if the app isn't present.

A Universal Link is an Apple-specific method introduced in iOS 9 that allows apps to handle standard HTTPS URLs rather than custom URL schemes. A Universal Link opens an app if it's installed or directs users to a website if the app isn't installed. For instance, if we have the app installed, https://mywebsite.com/user/999 could open a specific product page in your app; if you don't have the app installed, it would direct you to the website.

Configuration

To configure Universal Links on iOS, we need to make some changes in both the app and the backend of the website:

Prerequisites

  • HTTPS web domain you control (no redirects).
  • iOS 9+ deployment target.
  • App ID with Associated Domains enabled in your Apple Developer account.
  • Xcode project with capabilities access.

App-side setup

  1. Enable associated domains

    1. In Xcode, select Target → Signing & Capabilities.

    2. Click "+ Capability" and add Associated Domains.

    3. Under the new section, add an entry for each domain:

      applinks:mywebsite.com
      applinks:www.mywebsite.com
      

      You can include multiple domains; iOS will check each for a valid AASA file.

  2. Update the app's entitlements

    Xcode will automatically update your *.entitlements file. Under the hood it now contains:

    <key>com.apple.developer.associated-domains</key>
    <array>
    	<string>applinks:mywebsite.com</string>
    </array>

Server-side setup

  1. Create the apple-app-site-association (AASA) file

    At the root of your HTTPS server (or in /.well-known/), host a file called apple-app-site-association with no extension:

    {
    	"applinks": {
    		"details": [
    			{
    				"appID": "ABCDE12345.com.yourcompany.yourapp",
    				"paths": [
    					"/",
    					"/products/*",
    					"/blog/*",
    					"/user/profile"
    				]
    			}
    		]
    	}
    }

    Where:

    • appID = ${TeamID}.${BundleIdentifier} (e.g. ABCDE12345.com.yourcompany.yourapp).
    • paths = which URL paths your app handles. Use:
      • "/" for the home page.
      • "/products/*" to match all sub‐paths.
      • "*" to match everything on that domain.
      • "NOT /secret/*" to explicitly exclude.
  2. Serve over HTTPS

    The file must be served with application/json or application/pkc7-mime Content-Type with no redirects, as iOS fetches the file directly.

Depending on your project setup, implement one of:

  • iOS 13+ (SceneDelegate)

    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
    	guard let url = URLContexts.first?.url else { return }
    	handleUniversalLink(url)
    }
     
    private func handleUniversalLink(_ url: URL) {
    	// Parse URL.pathComponents or queryItems
    	// e.g., if url.path.starts(with: "/products/") …
    	print("Opened via Universal Link:", url)
    	// Route to the correct view controller…
    }
  • iOS 9-12 (AppDelegate)

    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
    {
    	guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
    		let url = userActivity.webpageURL
    	else { return false }
     
    	handleUniversalLink(url)
    	return true
    }

Test the setup

  1. Build and run the app on a real device (Universal Links don't work in the simulator).

  2. Send yourself an iMessage or email with an https://mywebsite.com/user/999 link.

  3. Tap the link:

    • If the app is installed → opens your app and calls your handler.
    • If not → opens Safari to the same URL on your website.
  4. Inspect device logs in Console.app filtering for swcd and AASA, to verify AASA fetch:

    swcd[xxxxx]: Finished fetching AASA file: https://mywebsite.com/apple-app-site-association

Troubleshooting

Symptom Possible Cause Fix
Link always opens in Safari, never in app AASA file missing, malformed, or wrong domain Check JSON validity & Content‑Type
Xcode entitlements don't list your domain Associated Domains capability not added Re-add under Signing & Capabilities
Partial path matching not working Paths in AASA too restrictive or syntax issue Verify your paths patterns

By following these steps, you can seamlessly route HTTPS links into your app and provide a smooth user experience.

Which one to use?

  1. Use Deep Links when...

    • You don't control a web domain

      If you just need two apps you own to talk to each other (or a single app to open itself), and there's no website fallback, a custom URL scheme (e.g. myapp://…) is the simplest choice.

    • You need wide, old‑OS support

      Custom schemes work all the way back to iOS 2 and Android 1.6. If you're targeting legacy devices (pre‑iOS 9/Android 6), schemes are your only option.

    • You want swifter in‑app routing

      There's no network fetch or cryptographic check—tapping a myapp:// link fires immediately. For some ultra‑fast, app‑only flows (e.g. internal tooling), that may matter.

    • You don't need a web fallback

      If falling back to a web page isn't critical (or if you don't have one), schemes let you skip the extra server setup.

  2. Use Automatic Links when...

    • You want a seamless web‑to‑app experience

      Tapping an https://… link opens your app if installed, or your website otherwise. No dialogs, no extra user steps.

    • You need robust security

      Apple verify you own the domain via an AASA (apple-app-site-association) or assetlinks.json file. That prevents other apps from hijacking your URLs.

    • Your link may be shared anywhere

      Universal Links work in Messages, Mail, Safari, Spotlight, Siri, and third‑party apps—just like any other HTTPS link.

    • You want analytics & fallback control

      Since it's a "real" URL, we can track clicks with standard web analytics and even A/B‑test redirects on the web side before handing off to the app.

Quick decision guide

Need / Constraint Deep Links Universal Links
OS support back to iOS 8.
No web domain or web fallback
Seamless “tap to open” in Messages, Safari, Siri, etc.
Domain‑verified security guardrails
Web analytics on every click
Minimal setup—just an app entitlement Requires server AASA/assetlinks

Bottom line

  • If you just need quick, internal, or legacy-device linking with no website, go with Deep Links.
  • If you have (or can create) a supporting HTTPS domain and want the most polished, secure, cross‑platform UX, go with Universal Links.