Glossary/Failure modes
Universal Links not opening the app
Definition
A Universal Link fails to open its app when iOS has no valid association for the domain, when the tapped URL does not match the association's path rules, or when the tap did not originate in a context where iOS honours Universal Links at all.
Those three categories fail in completely different places, and the fix for one does nothing for the others. This page is the iOS list; the Android equivalent is Android App Links not working, and if the file itself cannot be fetched, AASA file not found covers that layer in full. Start by working out which of the three categories you are in, because it eliminates most of the list immediately.
Which of the three categories are you in?
One command and one experiment narrow this down faster than reading the whole list.
# macOS, with the app installed on a connected simulator or the Mac itself.
# Prints the association state and the reason for any failure.
swcutil show --domain example.com
# Force a fresh fetch and print what went wrong
swcutil dl -d example.com
# What Apple's CDN is handing to devices right now
curl -sS https://app-site-association.cdn-apple.com/a/v1/example.com| Observation | Category | Causes to read |
|---|---|---|
swcutil shows no association, CDN returns nothing | The file | AASA file not found — stop here, it is that |
| Association exists; some URLs open the app, others don't | Matching | Causes 3 and 4 below |
| Association exists; no URL ever opens the app | Entitlement or handler | Causes 1, 2 and 8 |
| Works from Notes, fails from one particular app | Context | Causes 5, 6 and 7 |
| Worked yesterday, fails today | Regression | Universal Links stopped working |
The eight causes, in order
1. The Associated Domains entitlement is missing from the build that is actually running. The capability is enabled per provisioning profile, and a profile that predates you adding it will not carry it. The signature of this one is unmistakable: it works from Xcode and fails in TestFlight, or works in TestFlight and fails from the App Store. Regenerate the profile, confirm com.apple.developer.associated-domains is in the built binary's entitlements, and check the App ID has the Associated Domains capability enabled in the developer portal.
# What the shipped binary actually claims
codesign -d --entitlements :- /path/to/YourApp.app
# Inside an .ipa: unzip first, then inspect Payload/YourApp.app
unzip -q YourApp.ipa -d unpacked
codesign -d --entitlements :- unpacked/Payload/YourApp.app2. The entitlement lists a different host than the link. applinks:example.com does not cover www.example.com, and it does not cover shop.example.com. Every host needs its own entry — applinks:*.example.com covers subdomains on iOS 14 and later, but the apex still needs listing separately. If your site canonicalises the apex to www, the host you serve from and the host you entitled are probably not the same one.
3. The path does not match `components`, or an earlier rule shadows it. Matching stops at the first entry that matches, so an exclude placed after a broad "/": "*" is unreachable. Wildcards behave the way shell globs do not: * matches any run of characters *including* /, and ? matches exactly one. Component matching is also case-sensitive unless you set "caseSensitive": false, which catches teams whose URLs are mixed-case in the wild.
4. The link was tapped on a page of the same domain. This is deliberate iOS behaviour and it is the cause people lose the most time to. If the user is already on example.com in Safari and taps a link to example.com, iOS does not open the app — it assumes you would have routed them in-page if you wanted that. Test from Notes, Messages, or a different domain, never from your own site.
5. The navigation was programmatic rather than a tap. Universal Links are honoured for user-initiated navigation. Setting window.location.href from JavaScript, redirecting on page load, or triggering a synthetic click frequently does not open the app even when everything else is correct. A real <a href> that the user taps does. Any link-shortening or interstitial page that redirects on load is subject to this.
6. The tap happened inside an in-app browser. WKWebView does not open Universal Links at all — the host app has to intercept the navigation and call UIApplication.open itself, and most do not. That covers link taps inside Instagram, TikTok, LinkedIn, and a long tail of apps. SFSafariViewController is better behaved but still will not open the app that presented it. See deep links in in-app browsers.
7. A redirect stands between the tap and your domain. iOS evaluates the URL being *navigated to*, not where it ends up. A click tracker that answers on click.partner.com and 302s to example.com was never a Universal Link to your app — the tap was to the tracker's domain. Email click-tracking does this to nearly every marketing link; deep links in email clients covers the workarounds.
8. The app opens, but ignores the URL. Everything above is about whether the app launches. If it launches on the home screen instead of the destination, the association is fine and the handler is not. UIKit needs application(_:continue:restorationHandler:) with the NSUserActivityTypeBrowsingWeb activity type; SwiftUI needs .onOpenURL or .onContinueUserActivity. A cold launch and a warm resume arrive through different entry points, and handling only one is a common half-fix.
// UIKit
func application(
_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
let url = userActivity.webpageURL else { return false }
router.handle(url)
return true
}
// SwiftUI — .onOpenURL covers custom schemes; web URLs arrive as a user
// activity, so a SwiftUI app that only implements onOpenURL silently drops
// every Universal Link.
WindowGroup {
ContentView()
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
guard let url = activity.webpageURL else { return }
router.handle(url)
}
.onOpenURL { url in router.handle(url) }
}Testing without fooling yourself
Most iOS deep link tests confirm something other than what the tester believes. Knowing what each method actually exercises is the difference between a real diagnosis and an afternoon.
| Method | Proves | Blind to |
|---|---|---|
xcrun simctl openurl booted <url> | Your in-app routing | The association entirely — it always opens the app |
| Tapping a link in Notes or Messages | The real end-to-end path | Nothing, but tells you no reason |
| Tapping a link on your own site | Nothing useful | Cause 4 — this is expected to fail |
swcutil show --domain | The association state and its failure reason | Whether your handler routes correctly |
| The simulator generally | Little — it caches associations differently | Real device behaviour; confirm on hardware |
`simctl openurl` is not a Universal Links test
It hands the URL straight to the app the way a custom scheme would, bypassing association entirely. It will succeed on a build with no entitlement, no AASA file, and no association whatsoever — which is why it is the most commonly cited evidence that "the deep link works" on setups that are completely broken for users.
Universal Link tester
Causes 2 and 3 are matching problems, and matching is hard to reason about by reading JSON. Paste your association file and a real URL and it evaluates the components rules in order, tells you whether the URL opens the app or stays in Safari, and names the rule that decided — including the shadowed exclude that never gets reached.
Frequently asked questions
- Why do my Universal Links open Safari instead of my app?
- In order of likelihood: the Associated Domains entitlement is missing from the build that is running, the entitlement names a different host than the link uses, the path does not match the components rules in the AASA file, or the link was tapped on a page of the same domain, which iOS deliberately does not treat as a Universal Link. Running swcutil show --domain yourdomain.com tells you whether an association exists at all, which eliminates most of the list at once.
- Why don't Universal Links work when I tap a link on my own website?
- This is intentional iOS behaviour. When the user is already on your domain in Safari and taps a link to the same domain, iOS assumes you would have handled the navigation in-page if you wanted the app to open, so it does not launch the app. Test from Notes, Messages, or a page on a different domain instead.
- Do Universal Links work from JavaScript redirects?
- Usually not. iOS honours Universal Links for user-initiated navigation, so setting window.location.href, redirecting on page load, or dispatching a synthetic click frequently opens Safari rather than the app even when the association is correct. A real anchor element the user taps is reliable. This is why interstitial redirect pages and link shorteners that redirect on load often fail on iOS.
- Why does my app open but land on the home screen instead of the deep link destination?
- The association is working and the app-side handler is not. Universal Links arrive as an NSUserActivity with the NSUserActivityTypeBrowsingWeb activity type, not through onOpenURL. A SwiftUI app that only implements onOpenURL will launch but silently discard every web URL, and a UIKit app needs application(_:continue:restorationHandler:). Cold launch and warm resume also arrive through different entry points.
- Does xcrun simctl openurl test Universal Links?
- No. It delivers the URL directly to the app, bypassing domain association completely, so it succeeds even on a build with no entitlement and no apple-app-site-association file. It is a useful test of your in-app routing and worthless as a test of your setup. Tap a real link from Notes on a physical device instead.
- Do Universal Links work through redirects?
- No. iOS evaluates the URL being navigated to, not the one it eventually resolves to, so a link that points at a click tracker and redirects to your domain is a link to the tracker's domain as far as Universal Links are concerned. Email click tracking and most ad networks introduce exactly this redirect, which is why marketing links often fail while the same URL pasted into Notes works.
Related terms
- AASA file not found — An AASA file not found error means Apple's content delivery network could not retrieve a usable apple-app-site-association file from a domain, which disables Universal Links for that domain entirely.
- Universal Links stopped working — Universal Links that previously worked and no longer do have almost always been broken by a change outside the app: a per-domain user preference, a signing or infrastructure change, or Apple's CDN refreshing its cached copy of a file that was already broken.
- Apple App Site Association (AASA) — The apple-app-site-association file is a JSON document hosted at a domain's /.well-known/ path that tells iOS which app is allowed to handle which URLs on that domain.