iOS Universal Links: Complete Setup Guide (2026)

iOS Universal Links let a URL open directly inside your app instead of a browser, with no redirect and no custom scheme required. When a user taps a link to your domain, iOS checks if your app is installed and registered to handle that domain. If it is, the app opens immediately to the right screen. If not, Safari opens the URL as a normal webpage.

This guide covers the complete setup from start to finish, including the apple-app-site-association file, Xcode entitlements, common failure modes, and how to validate everything before shipping.

How Universal Links Work

When your app is installed, iOS downloads and caches the apple-app-site-association (AASA) file from your domain. This file lists which URL paths your app is allowed to handle. When a user taps a link to your domain, iOS checks its cached AASA data. If the path matches, the app opens. If not, the link opens in Safari.

The key difference from custom URI schemes (like myapp://) is that Universal Links use real HTTPS URLs. This means they work as normal web URLs when the app is not installed, and they cannot be hijacked by another app claiming the same scheme.

Prerequisites

Before starting, you need:

A domain you control with HTTPS (not HTTP) An Apple Developer account Your app's Bundle ID (e.g. com.yourcompany.yourapp) Your Apple Team ID (found in Apple Developer portal under Membership)

Step 1: Create the apple-app-site-association File

The AASA file tells iOS which paths your app handles. Create a file with no file extension named apple-app-site-association with this structure:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAMID.com.yourcompany.yourapp"],
        "components": [
          {
            "/": "/invite/*",
            "comment": "Matches all invite paths"
          },
          {
            "/": "/product/*",
            "comment": "Matches all product paths"
          }
        ]
      }
    ]
  }
}

Replace TEAMID with your 10-character Apple Team ID and com.yourcompany.yourapp with your Bundle ID.

Path matching rules

/invite/* matches any path starting with /invite/ / alone matches all paths on the domain NOT / in a component excludes a path from matching Paths are case-sensitive

The older format (iOS 12 and below)

If you need to support iOS 12, use the legacy format alongside the modern one:

{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAMID.com.yourcompany.yourapp",
        "paths": ["/invite/*", "/product/*"]
      }
    ]
  }
}

For maximum compatibility, include both formats in the same file.

Step 2: Serve the AASA File

The file must be served at:

https://yourdomain.com/.well-known/apple-app-site-association

or at the root:

https://yourdomain.com/apple-app-site-association

Apple checks both locations. The .well-known/ path is preferred.

Serving requirements

HTTPS only (HTTP is rejected) Content-Type must be application/json No redirects on the AASA URL itself (Apple does not follow redirects) No authentication or access restrictions The file must be publicly accessible

If you use Flinku, the AASA file is served automatically for your subdomain (yourapp.flku.dev) and any custom domain you add. You do not need to manage it manually.

Step 3: Add the Associated Domains Entitlement in Xcode

Open your project in Xcode. Select your app target, go to Signing & Capabilities, and click the plus button to add Associated Domains.

Add an entry for each domain your app handles:

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

If you use a deep linking platform with a custom subdomain, add that too:

applinks:yourapp.flku.dev

The applinks: prefix is required. Do not include https:// or trailing slashes.

Alternative Developer Mode domains (iOS 16+)

For testing on a local or staging server, you can use:

applinks:yourdomain.com?mode=developer

This bypasses Apple's CDN cache and fetches the AASA file directly from your server during development.

Step 4: Handle Universal Links in Your App

In AppDelegate.swift, implement the method that receives Universal Links:

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

For SwiftUI apps, use the .onOpenURL modifier:

@main
struct MyApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .onOpenURL { url in
          handleDeepLink(url)
        }
    }
  }
}

Handling cold start vs foreground

When the app is not running and a Universal Link opens it, the link arrives via application(_:continue:restorationHandler:) during app launch.

When the app is already running in the background and a Universal Link is tapped, the same delegate method is called.

Test both scenarios explicitly before shipping.

Step 5: Validate Your Setup

Check the AASA file is reachable

curl -I https://yourdomain.com/.well-known/apple-app-site-association

The response should show HTTP/2 200 and content-type: application/json.

Validate with Apple's CDN

Apple caches AASA files through its own CDN. You can check what Apple has cached using the App Search API validation tool:

https://app-site-association.cdn-apple.com/a/v1/yourdomain.com

If this returns a different version than your live file, Apple's CDN has not yet refreshed. This can take up to 24 hours after a change.

Use Flinku's AASA Validator

Flinku has a free AASA Validator at flinku.dev/tools/aasa-validator that checks 7 validation points including content type, JSON validity, Team ID format, and path structure.

Common Failures and How to Fix Them

Universal Links open Safari instead of the app

The most common cause is a mismatch between the domain in your entitlements and the domain in the AASA file. Check that the appIDs field uses your exact Team ID and Bundle ID with no typos. Also verify the path in the URL matches a pattern in components (or paths in the legacy format).

Links work on Wi-Fi but not cellular

Apple fetches the AASA file at install time. If the device had no internet during install, the AASA was never fetched. The user needs to reinstall while connected.

Works in development, fails in production

Developer mode domains (?mode=developer) bypass the CDN. In production, Apple serves from its CDN cache. If you changed your AASA file recently, the CDN may still have the old version. Wait up to 24 hours or use a new domain path to force a cache miss.

Entitlement not found during App Store review

The Associated Domains entitlement must be enabled in your App ID in the Apple Developer portal under Certificates, Identifiers & Profiles, not just in Xcode. Check both places.

AASA file returns a redirect

Apple does not follow redirects when fetching the AASA file. If your server redirects HTTP to HTTPS, or redirects the root to www, make sure the AASA URL itself serves a direct 200 response, not a 301.

Frequently Asked Questions

Do Universal Links work without an internet connection?

The AASA file is cached at install time. After that, opening Universal Links does not require a network request. The cached data is used.

How long does Apple cache the AASA file?

Apple's CDN caches AASA files and refreshes them periodically. Changes can take up to 24 hours to propagate. For immediate testing, use developer mode or reinstall the app.

Can I use Universal Links without a deep linking platform?

Yes. You host the AASA file yourself and handle the URL in your app delegate. The limitation is deferred deep linking, where the app is not installed yet. Universal Links alone cannot handle that case because there is no app to receive the link. For deferred deep linking, you need a platform like Flinku that fingerprints the user before install and matches them after.

What is the difference between Universal Links and App Links?

Universal Links are the iOS mechanism. App Links are the Android equivalent, using assetlinks.json instead of AASA. They work similarly but have different file formats, path matching rules, and validation tools.

Do Universal Links require App Store distribution?

No. Universal Links work with TestFlight builds and development builds as long as the entitlement is configured and the AASA file is valid.

Getting Started with Flinku

If you use Flinku for deep linking, the AASA file is served automatically for your subdomain and any custom domain. You add the associated domain entitlement in Xcode pointing to your Flinku subdomain and Flinku handles the rest, including deferred deep linking for users who do not have the app installed yet.

Validate your AASA file for free or start a free Flinku project.