Universal Links let your website URLs open directly in your iOS app. When users tap a link, iOS checks if you have an associated app and opens it seamlessly—no Safari redirect, no custom URL schemes.
Key Takeaways
- 1Host an apple-app-site-association file on your web server
- 2Add Associated Domains entitlement to your app
- 3Handle incoming URLs in your app delegate
- 4Works with standard https:// URLs—no custom schemes needed
- 5Falls back to Safari if app isn't installed
How Universal Links Work
Universal Links rely on a trust relationship between your website and your app. iOS verifies this relationship by downloading a special file from your web server when the app is installed. Once verified, tapping links to your domain opens the app directly instead of Safari.
App Installation
iOS downloads your apple-app-site-association file when the app installs
User Taps Link
When a user taps a link to your domain, iOS checks for association
App Opens
If associated, iOS opens your app with the URL
The verification happens automatically—users do not see any prompts. Let's look at how to set up each piece of this system.
Apple App Site Association File
The Apple App Site Association (AASA) file tells iOS which apps are authorized to handle URLs for your domain, and which URL paths should trigger the app. Host this JSON file at /.well-known/apple-app-site-association (no file extension).
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAMID.com.example.app",
"paths": [
"/products/*",
"/users/*",
"/share/*",
"NOT /api/*",
"NOT /admin/*"
]
}
]
}
}The appID combines your Team ID (from the Apple Developer portal) with your app's bundle ID. The paths array specifies which URL paths should open your app, with support for wildcards and exclusions using the NOT prefix.
AASA Requirements
The AASA file must meet specific requirements for iOS to accept it. Getting these wrong is the most common cause of Universal Links not working.
| Requirement | Details |
|---|---|
| Location | /.well-known/apple-app-site-association OR /apple-app-site-association |
| Content-Type | application/json |
| HTTPS | Must be served over HTTPS with valid certificate |
| No redirects | File must be served directly, no redirects allowed |
| File size | Max 128 KB unsigned, 1 MB signed |
Path Patterns
Path patterns let you control exactly which URLs should open your app. Use wildcards for dynamic segments, and the NOT prefix to exclude paths like API endpoints that should always load in the browser.
| Pattern | Matches |
|---|---|
* | Any string (within path component) |
? | Any single character |
/products/* | /products/123, /products/abc |
NOT /api/* | Exclude paths from association |
/search?* | /search?q=term (query strings) |
With the server-side AASA file in place, you need to configure your Xcode project to declare which domains your app handles.
Xcode Configuration
Adding Associated Domains capability in Xcode tells iOS to check your web server for the AASA file when the app installs. This two-way verification ensures only you can claim your domain.
Add Associated Domains capability
Target → Signing & Capabilities → + Capability → Associated Domains
Add your domain
Add: applinks:example.com
# In Associated Domains entitlement:
applinks:example.com
applinks:www.example.com
applinks:*.example.com # Wildcard subdomainEach entry in the Associated Domains list must start with applinks: followed by the domain. You can use wildcards for subdomains, but the wildcard must be at the beginning.
Handling Links in Your App
When a user taps a Universal Link and your app opens, you receive the URL through your app's entry point. Parse the URL to determine which screen to show and extract any parameters like product IDs or user names.
SwiftUI
In SwiftUI, use the .onOpenURL modifier to receive Universal Links. This works for both cold starts (app was not running) and warm starts (app was in background).
import SwiftUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleUniversalLink(url)
}
}
}
func handleUniversalLink(_ url: URL) {
// Parse the URL and navigate
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return
}
if components.path.hasPrefix("/products/") {
let productId = components.path.replacingOccurrences(of: "/products/", with: "")
// Navigate to product
navigateToProduct(productId)
}
}
}The handler parses the URL using URLComponents and routes based on the path. You can extract path segments, query parameters, or fragments to determine the destination screen.
UIKit (AppDelegate)
For UIKit apps, handle Universal Links in the AppDelegate's continue userActivity method. Check that the activity type is NSUserActivityTypeBrowsingWeb to confirm it's a web link.
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else {
return false
}
// Handle the URL
return handleUniversalLink(url)
}
func handleUniversalLink(_ url: URL) -> Bool {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
return false
}
// Route based on path
switch components.path {
case let path where path.hasPrefix("/products/"):
let id = path.replacingOccurrences(of: "/products/", with: "")
navigateToProduct(id)
return true
case let path where path.hasPrefix("/users/"):
let id = path.replacingOccurrences(of: "/users/", with: "")
navigateToUser(id)
return true
default:
return false
}
}The switch statement routes to different handlers based on the path prefix. Return true if your app handled the link, or false to let iOS handle it (which typically opens Safari).
Testing Universal Links
Testing Universal Links can be tricky because iOS caches the AASA file and has specific requirements about how links must be tapped. Follow these steps to avoid common pitfalls.
Validate AASA file
Use Apple's validator: search.developer.apple.com/appsearch-validation-tool
Test on real device
Universal Links don't work in Simulator. Use a physical device.
Reinstall the app
iOS downloads AASA at install time. Reinstall after server changes.
Troubleshooting
When Universal Links are not working, the problem is usually in one of a few common areas. The table below covers the issues we see most often and how to fix them.
| Issue | Solution |
|---|---|
| Links open in Safari | Check AASA file is valid JSON, served over HTTPS without redirects |
| Works sometimes | User may have long-pressed and chosen "Open in Safari"—resets preference |
| AASA not downloading | Check Content-Type header, no redirects, valid SSL certificate |
| Wrong app opens | Check Team ID in AASA matches your app's Team ID exactly |
Try the URL Builder
Use our iOS Deep Link template to explore deep link URL patterns. You can experiment with different path structures and see how they would map to your app's screens.