{"id":454,"date":"2026-03-15T17:00:00","date_gmt":"2026-03-15T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=454"},"modified":"2026-03-07T04:35:57","modified_gmt":"2026-03-07T09:35:57","slug":"universal-links-ios-17-changes","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/universal-links-ios-17-changes\/","title":{"rendered":"Universal Links in iOS 17 and Later: What Changed"},"content":{"rendered":"\n<p>If your app uses Universal Links and you have not revisited the setup since iOS 16, there are real changes to deal with. iOS 17 brought a revised AASA format, stricter CDN caching behavior, new developer-facing diagnostics, and some SwiftUI quality-of-life improvements. None of these changes break Universal Links outright, but ignoring them can mean links that silently fall back to Safari instead of opening your app.<\/p>\n\n\n\n<p>This article covers what changed, why Apple made those changes, and what you need to update.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">A Quick Refresher on How Universal Links Work<\/h2>\n\n\n\n<p>When a user taps a link, iOS checks whether any installed app claims that domain. The claim is established through a two-part contract: your app declares the domain in its entitlements, and your server hosts an Apple App Site Association (AASA) file at <code>https:\/\/yourdomain.com\/.well-known\/apple-app-site-association<\/code>.<\/p>\n\n\n\n<p>Apple&#39;s servers fetch and cache that AASA file when the app is installed (and periodically after that). iOS then uses the cached copy to decide whether to route the link to your app or fall through to Safari.<\/p>\n\n\n\n<p>For a deeper look at the fundamentals, our <a href=\"https:\/\/tolinku.com\/blog\/universal-links-everything-you-need-to-know\/\">complete guide to Universal Links<\/a> covers everything from tap to app open, or see the <a href=\"https:\/\/tolinku.com\/docs\/concepts\/universal-links\/\">Tolinku Universal Links concept guide<\/a> for the technical reference.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What Changed in iOS 17<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. The <code>components<\/code> Syntax Replaces <code>paths<\/code><\/h3>\n\n\n\n<p>The biggest format change in iOS 17 is the introduction of a <code>components<\/code> array inside each app entry, alongside deprecation of the older <code>paths<\/code> array. The two can coexist in the same file, so you do not need to drop <code>paths<\/code> immediately, but <code>components<\/code> is the format Apple will continue developing.<\/p>\n\n\n\n<p>Here is what the older <code>paths<\/code> format looks like:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;apps&quot;: [],\n    &quot;details&quot;: [\n      {\n        &quot;appID&quot;: &quot;TEAMID.com.example.app&quot;,\n        &quot;paths&quot;: [\n          &quot;\/products\/*&quot;,\n          &quot;\/checkout&quot;,\n          &quot;NOT \/admin\/*&quot;\n        ]\n      }\n    ]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>And here is the equivalent using <code>components<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;applinks&quot;: {\n    &quot;details&quot;: [\n      {\n        &quot;appIDs&quot;: [&quot;TEAMID.com.example.app&quot;],\n        &quot;components&quot;: [\n          {\n            &quot;\/&quot;: &quot;\/products\/*&quot;,\n            &quot;comment&quot;: &quot;Matches all product pages&quot;\n          },\n          {\n            &quot;\/&quot;: &quot;\/checkout&quot;\n          },\n          {\n            &quot;\/&quot;: &quot;\/admin\/*&quot;,\n            &quot;exclude&quot;: true\n          }\n        ]\n      }\n    ]\n  }\n}\n<\/code><\/pre>\n\n\n\n<p>A few things to notice. First, <code>appID<\/code> (singular) becomes <code>appIDs<\/code> (plural array), which lets you list multiple app identifiers in one block without duplicating the component rules. Second, exclusions move from the <code>NOT \/path<\/code> string syntax to an explicit <code>&quot;exclude&quot;: true<\/code> key, which is much less error-prone. Third, you can also match on query parameters and fragment identifiers using <code>&quot;?&quot;<\/code> and <code>&quot;#&quot;<\/code> keys respectively:<\/p>\n\n\n\n<pre><code class=\"language-json\">{\n  &quot;\/&quot;: &quot;\/share\/*&quot;,\n  &quot;?&quot;: { &quot;ref&quot;: &quot;?*&quot; },\n  &quot;comment&quot;: &quot;Only match share links with a ref parameter&quot;\n}\n<\/code><\/pre>\n\n\n\n<p>Apple&#39;s <a href=\"https:\/\/developer.apple.com\/documentation\/xcode\/supporting-associated-domains\" rel=\"nofollow noopener\" target=\"_blank\">Supporting Associated Domains documentation<\/a> has the full reference for component matching syntax.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">2. CDN Caching Is Now More Aggressive<\/h3>\n\n\n\n<p>Before iOS 17, Apple&#39;s CDN fetched your AASA file and cached it for up to 24 hours. Starting with iOS 17, Apple tightened the caching rules in two directions: the TTL can be longer under stable conditions, but Apple also introduced a faster propagation path for apps submitted through the App Store. For a detailed look at how Apple&#39;s CDN fetches and caches your AASA file, see <a href=\"https:\/\/tolinku.com\/blog\/apple-cdn-validation-universal-links\/\">Apple CDN validation for Universal Links<\/a>.<\/p>\n\n\n\n<p>When you submit a new build to App Store Connect, Apple re-fetches your AASA file as part of the submission pipeline. This means that for App Store distributed apps, changes to your AASA file now reach users faster after a new app release, even if the CDN cache has not expired organically.<\/p>\n\n\n\n<p>The practical implication: if you are testing AASA changes during development, you cannot rely on cache expiry alone. Use TestFlight and the developer diagnostics described below. In production, coordinate AASA file changes with an app release when possible so the re-fetch happens at a predictable time.<\/p>\n\n\n\n<p>Your AASA endpoint must continue to return <code>Content-Type: application\/json<\/code> (not <code>application\/pkcs7-mime<\/code>, which was required years ago and has been optional since iOS 9.3.1). It must be served over HTTPS with a valid certificate.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. Alternate Modes Are Now Fully Supported<\/h3>\n\n\n\n<p>The <code>applinks<\/code> service type has always been the main use case, but iOS 17 brings stable, documented support for additional modes in the Associated Domains entitlement:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>webcredentials<\/code> for AutoFill credential sharing<\/li>\n<li><code>activitycontinuation<\/code> for Handoff<\/li>\n<li><code>appclips<\/code> for App Clips<\/li>\n<\/ul>\n\n\n\n<p>These were available before, but the AASA format updates mean they can now live alongside <code>applinks<\/code> in a single file with cleaner syntax. If you are using any of these services, verify your file structure against the <a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2023\/10055\/\" rel=\"nofollow noopener\" target=\"_blank\">Associated Domains WWDC 2023 session<\/a> notes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">4. New Developer Tools for Diagnostics<\/h3>\n\n\n\n<p>iOS 17 ships a proper diagnostic flow for Universal Links in Developer Mode. To access it:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Go to Settings &gt; Developer &gt; Universal Links.<\/li>\n<li>Enable &quot;Diagnostics.&quot;<\/li>\n<li>Tap any link in Safari or Mail.<\/li>\n<\/ol>\n\n\n\n<p>iOS will show you in real time whether it matched the link to an app, which AASA rule matched, and why a link fell through to Safari if it did. Before iOS 17, debugging this required Console.app on a Mac and hunting for <code>swcd<\/code> log entries, which was tedious.<\/p>\n\n\n\n<p>For command-line debugging, the <code>swcutil<\/code> tool also gained more readable output. Running <code>xcrun swcutil display<\/code> on a connected device shows you the full approved-domains list and the AASA payload Apple has cached for each app.<\/p>\n\n\n\n<p>Apple&#39;s <a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2023\/10051\/\" rel=\"nofollow noopener\" target=\"_blank\">WWDC 2023 &quot;What&#39;s new in universal links&quot; session<\/a> walks through the new diagnostic tooling in detail.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. Safari Behavior Changes<\/h3>\n\n\n\n<p>In iOS 17, Safari became more selective about showing the Smart App Banner and the &quot;Open in App&quot; prompt for Universal Link domains. Safari now checks the same AASA cache that iOS uses for link routing, and if your AASA file is malformed or missing required fields, Safari will suppress the banner entirely rather than showing it with a fallback.<\/p>\n\n\n\n<p>This is a stricter enforcement of existing rules, not a new policy, but it has caught a lot of teams off guard. If your Smart App Banner disappeared after an iOS 17 update, your AASA file is worth checking first.<\/p>\n\n\n\n<p>Safari also no longer shows the &quot;Open in App&quot; prompt for links that land on pages explicitly excluded from Universal Link handling (via <code>&quot;exclude&quot;: true<\/code> in <code>components<\/code> or <code>NOT<\/code> in <code>paths<\/code>). This is correct behavior, but if your exclusions were too broad, you may have inadvertently suppressed the prompt on pages where you want it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Migrating Your AASA File<\/h2>\n\n\n\n<p>Here is a migration checklist for teams moving from the <code>paths<\/code> format to <code>components<\/code>.<\/p>\n\n\n\n<p><strong>Step 1: Add <code>components<\/code> alongside <code>paths<\/code>.<\/strong><\/p>\n\n\n\n<p>Keep <code>paths<\/code> in place for devices running iOS 16 and earlier. Add a <code>components<\/code> array for iOS 17+ behavior. iOS 17 will use <code>components<\/code> and ignore <code>paths<\/code> when both are present; older versions will use <code>paths<\/code>.<\/p>\n\n\n\n<p><strong>Step 2: Convert exclusions.<\/strong><\/p>\n\n\n\n<p>Replace every <code>&quot;NOT \/some\/path&quot;<\/code> entry in <code>paths<\/code> with a matching component entry that has <code>&quot;exclude&quot;: true<\/code>. Be precise. The <code>NOT<\/code> syntax always did prefix matching, which was sometimes too broad.<\/p>\n\n\n\n<p><strong>Step 3: Switch to the <code>appIDs<\/code> array.<\/strong><\/p>\n\n\n\n<p>Replace <code>&quot;appID&quot;: &quot;TEAMID.bundle&quot;<\/code> with <code>&quot;appIDs&quot;: [&quot;TEAMID.bundle&quot;]<\/code>. If you support multiple targets (for example, a main app and an App Clip), list them both here instead of duplicating the entire entry.<\/p>\n\n\n\n<p><strong>Step 4: Validate the file.<\/strong><\/p>\n\n\n\n<p>Apple&#39;s <a href=\"https:\/\/branch.apple.com\/aasa-validator\" rel=\"nofollow noopener\" target=\"_blank\">Associated Domains validator<\/a> accepts your domain and checks the AASA file format and reachability. Our <a href=\"https:\/\/tolinku.com\/blog\/aasa-file-setup\/\">AASA file setup guide<\/a> walks through common validation errors and hosting requirements, and the <a href=\"https:\/\/tolinku.com\/docs\/developer\/universal-links\/\">Tolinku developer guide for Universal Links<\/a> covers the full specification.<\/p>\n\n\n\n<p><strong>Step 5: Test on a real device.<\/strong><\/p>\n\n\n\n<p>Simulator does not fully replicate Universal Link behavior. Use a physical device with Developer Mode enabled and the new diagnostics in Settings. Confirm that your intended paths open the app and your excluded paths fall through to Safari correctly.<\/p>\n\n\n\n<p><strong>Step 6: Submit a new build.<\/strong><\/p>\n\n\n\n<p>Once your AASA file is updated on the server, submit a new build to App Store Connect to trigger Apple&#39;s re-fetch of your file. This shortens the propagation delay significantly.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">SwiftUI Deep Link Handling<\/h2>\n\n\n\n<p>iOS 17 also brought improvements to <code>onOpenURL<\/code> and the new <code>NavigationStack<\/code>-based routing patterns. If you are still using <code>UIApplicationDelegate<\/code>&#39;s <code>application(_:continue:restorationHandler:)<\/code> to handle incoming Universal Links, you are not doing anything wrong, but SwiftUI now has cleaner native primitives.<\/p>\n\n\n\n<p>The <code>onOpenURL<\/code> modifier on a <code>NavigationStack<\/code> or root view handles Universal Links without needing to bridge through UIKit:<\/p>\n\n\n\n<pre><code class=\"language-swift\">NavigationStack(path: $navigationPath) {\n    ContentView()\n        .navigationDestination(for: AppRoute.self) { route in\n            routeView(for: route)\n        }\n}\n.onOpenURL { url in\n    if let route = AppRoute(url: url) {\n        navigationPath.append(route)\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>The key improvement in iOS 17 is that <code>onOpenURL<\/code> now fires correctly when the app is launched cold from a Universal Link, not just when the app is already in the foreground. Previous versions of SwiftUI had a race condition where cold-launch Universal Links would sometimes arrive before the view hierarchy was ready to handle them. That race is resolved in iOS 17.<\/p>\n\n\n\n<p>If you are implementing deferred deep linking (routing users through an app install to a specific screen), the patterns described in the <a href=\"https:\/\/tolinku.com\/features\/deep-linking\">Tolinku deep linking features guide<\/a> are still the right approach since iOS itself does not carry the link through installation.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What You Do Not Need to Change<\/h2>\n\n\n\n<p>A few things that caused concern when iOS 17 landed are actually non-issues.<\/p>\n\n\n\n<p>The <code>&quot;apps&quot;: []<\/code> key inside <code>applinks<\/code> was always a legacy placeholder and can be omitted entirely in the new format. If it is still in your file, that is fine; iOS ignores it.<\/p>\n\n\n\n<p>The file location has not changed. <code>\/.well-known\/apple-app-site-association<\/code> is still the canonical path, and the root-level path <code>\/apple-app-site-association<\/code> still works as a fallback.<\/p>\n\n\n\n<p>The entitlements format in Xcode has not changed. Associated Domains are still configured the same way in your target&#39;s Signing and Capabilities settings.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>For a broader comparison of how Universal Links behavior has evolved across iOS releases, see our article on <a href=\"https:\/\/tolinku.com\/blog\/universal-links-ios-version-differences\/\">Universal Links iOS version differences<\/a>.<\/p>\n\n\n\n<p>iOS 17 did not break Universal Links, but it introduced enough changes that a passive setup can start showing cracks. The <code>components<\/code> syntax is cleaner and more powerful than <code>paths<\/code>, the new diagnostic tools make debugging far less painful, and the App Store re-fetch shortens the propagation delay when you update your AASA file. If you have not revisited your setup since 2022 or 2023, now is a good time to run through the migration checklist above and validate your AASA file format against Apple&#39;s current documentation.<\/p>\n\n\n\n<p>The Apple developer documentation for <a href=\"https:\/\/developer.apple.com\/documentation\/xcode\/supporting-associated-domains\" rel=\"nofollow noopener\" target=\"_blank\">Supporting Associated Domains<\/a> and the <a href=\"https:\/\/developer.apple.com\/videos\/play\/wwdc2023\/10051\/\" rel=\"nofollow noopener\" target=\"_blank\">WWDC 2023 Universal Links session<\/a> are the authoritative references for all of the changes covered here.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Review the changes Apple made to Universal Links in iOS 17+. Updated verification, new behaviors, and migration steps for your app.<\/p>\n","protected":false},"author":2,"featured_media":453,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Universal Links in iOS 17 and Later: What Changed","rank_math_description":"Review the changes Apple made to Universal Links in iOS 17+. Updated verification, new behaviors, and migration steps for your app.","rank_math_focus_keyword":"universal links iOS 17","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-universal-links-ios-17-changes.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-universal-links-ios-17-changes.png","footnotes":""},"categories":[12],"tags":[76,79,20,75,24,31,78,22,77],"class_list":["post-454","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ios","tag-aasa","tag-app-store","tag-deep-linking","tag-developer-tools","tag-ios","tag-swift","tag-swiftui","tag-universal-links","tag-wwdc"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/454","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=454"}],"version-history":[{"count":2,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/454\/revisions"}],"predecessor-version":[{"id":2784,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/454\/revisions\/2784"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/453"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=454"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=454"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=454"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}