Deeplinkly

Glossary/Implementation artifacts

Apple App Site Association (AASA)

Definition

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.

It is the iOS half of the domain-to-app handshake behind Universal Links — Android's equivalent is assetlinks.json. Apple fetches the file when your app is installed, caches the result, and from then on decides every link tap against that cached copy. Nothing about a Universal Link works until this file is present, correctly shaped, and served under a specific and unforgiving set of rules.

What the file looks like

This is a complete, current AASA file. It has no file extension — not .json — and it is not signed. Signing was dropped in iOS 9.3; if you find a guide telling you to run openssl smime, it predates that change by a decade.

/.well-known/apple-app-site-association
{
  "applinks": {
    "details": [
      {
        "appIDs": ["ABCDE12345.com.example.shop"],
        "components": [
          {
            "/": "/products/*",
            "comment": "Open product pages in the app"
          },
          {
            "/": "/orders/*",
            "?": { "guest": "true" },
            "exclude": true,
            "comment": "Guest order lookup stays on the web"
          },
          {
            "/": "/help/*",
            "exclude": true,
            "comment": "Support content stays on the web"
          }
        ]
      }
    ]
  },
  "webcredentials": {
    "apps": ["ABCDE12345.com.example.shop"]
  },
  "appclips": {
    "apps": ["ABCDE12345.com.example.shop.Clip"]
  }
}

ABCDE12345.com.example.shop is an App ID: the App ID Prefix, a dot, then the bundle identifier. The prefix is usually your Team ID but is not guaranteed to be — App IDs created before Team ID unification, or migrated between accounts, keep their original prefix. Copy it from the Identifiers list in your Apple Developer account rather than assuming.

Three keys, three purposes

applinks handles Universal Links. webcredentials is what lets the app share saved passwords and passkeys with the website — required for AutoFill, and unrelated to link routing. appclips declares an App Clip. Only applinks is needed for deep linking; the other two are in the same file because they are all Associated Domains.

appIDs and components vs. the legacy format

There are two AASA formats in the wild, and the older one still works — which is why so much copy-pasted config is a decade out of date. The modern format replaced appID/paths with appIDs/components in iOS 13, and the difference is not cosmetic: paths could only match on the path, while components can match query parameters and fragments too.

The two AASA formats. Prefer components unless you still support iOS 12.
Legacy (iOS 9–12)Modern (iOS 13+)
App ID keyappID (one string)appIDs (array — several apps can share one entry)
Matching keypaths (array of strings)components (array of objects)
Path match"/products/*"{ "/": "/products/*" }
Exclude a path"NOT /help/*"{ "/": "/help/*", "exclude": true }
Match a query stringNot possible{ "?": { "id": "?*" } }
Match a fragmentNot possible{ "#": "section" }
Case sensitivityAlways case-sensitive"caseSensitive": false per component
apps: [] requiredYes — omitting it broke parsingNo — drop it

You can ship both keys in one file. iOS 13 and later read components and ignore paths; iOS 12 does the reverse. Wildcards are the same in both: * matches any number of characters including /, and ? matches exactly one character.

Order matters, and the first match wins

components is evaluated top to bottom and evaluation stops at the first entry that matches. An exclude rule placed after a broad "/": "*" will never be reached. Put your exclusions first — and check the result against real URLs with the Universal Link tester rather than reasoning about it, because a shadowed rule looks completely correct in the file.

The hosting rules, and why each one bites

Apple does not fetch this file from your server directly. A CDN operated by Apple fetches it, and your device asks the CDN. That indirection is the source of most AASA problems, because the CDN is strict in ways a browser is not — it will not follow a redirect, and it will not tell you why it gave up.

Every constraint on serving the file, and the symptom when you miss it.
RuleWhySymptom when violated
Served from /.well-known/apple-app-site-associationStandardised location per RFC 8615File not found; links open in Safari
No file extensionThe filename is matched literally404 — apple-app-site-association.json is a different URL
Content-Type: application/jsonThe CDN validates the type before parsingFetched but discarded, silently
HTTPS with a valid, trusted certificateNo exceptions, including for stagingFetch fails outright
Zero redirects — 200 on the first requestThe CDN does not follow 301 or 302The single most common cause of a broken setup
128 KB or smallerHard cap enforced by AppleWhole file ignored once you cross it
Reachable with no authenticationThe CDN sends no cookies or credentialsA WAF or bot rule returning 403 breaks it
Hosted on the exact host in your entitlementexample.com and www.example.com are different domainsWorks on one host, dead on the other

Check what the CDN actually holds rather than what your server returns. This is the copy your users' devices are given:

Verify the CDN copy and your origin
# What Apple's CDN is serving to devices right now
curl -sS https://app-site-association.cdn-apple.com/a/v1/example.com

# What your origin returns — -I for headers, -L to expose any redirect
curl -sSIL https://example.com/.well-known/apple-app-site-association

# On macOS: force a fetch and print why it failed
swcutil dl -d example.com

The `.well-known` directory disappears in deploys

Directories beginning with a dot are excluded by default by a surprising number of build and upload pipelines. If the file is committed but 404s in production, check that the deployed artifact still contains .well-known/ before you debug anything else. See aasa-file-not-found for the full ordered checklist.

Caching, and why your change hasn't taken effect

There are two caches between your edit and a working link, and they expire on different schedules. Apple's CDN refreshes its copy of your file periodically — allow up to 24 hours. Separately, each device caches the association it was given, and re-fetches on app install, on app update, and occasionally in the background. Editing the file does not push anything to installed devices.

For development, add ?mode=developer to the entitlement. That bypasses the CDN entirely and makes the device fetch straight from your domain, so edits take effect immediately — after enabling Settings → Developer → Associated Domains Development on the device.

Associated Domains entitlement
<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:example.com</string>
  <string>applinks:www.example.com</string>
  <string>webcredentials:example.com</string>

  <!-- Development builds only: bypasses Apple's CDN, no propagation delay.
       Must be removed before submitting to the App Store. -->
  <string>applinks:staging.example.com?mode=developer</string>
</array>

Subdomains are not inherited

applinks:example.com does not cover www.example.com or shop.example.com. Each host needs its own entitlement string and its own AASA file. You can use applinks:*.example.com on iOS 14 and later to cover subdomains, but the apex still needs its own entry.

Getting it right the first time

Most AASA files are wrong in one of five ways, and all five are mechanical: the App ID Prefix is the Team ID when it shouldn't be, the file has a .json extension, components is nested one level too deep, a broad wildcard shadows the exclusions below it, or the file is valid but sitting behind a redirect. None of them produce an error message — the link simply opens Safari.

  1. Copy the App ID from the Identifiers list in your Apple Developer account, prefix included.
  2. Write the exclusions above the broad matches in components.
  3. Deploy, then curl -sSIL the URL and confirm a single 200 with Content-Type: application/json and no redirect in the chain.
  4. Wait for Apple's CDN, or use ?mode=developer on a dev build to skip the wait.
  5. Delete and reinstall the app — association is fetched at install, so an already-installed build keeps the old answer.

AASA generator

Enter your App ID and the paths you want the app to handle, and get a valid apple-app-site-association file with the modern components format. It also fetches and validates an existing file on a live domain, checking the redirect chain, content type, and size against every rule in the table above.

Open the aasa generator

Frequently asked questions

Where should the apple-app-site-association file be hosted?
At https://yourdomain.com/.well-known/apple-app-site-association, served over HTTPS with a valid certificate, with a Content-Type of application/json, no file extension, and no redirects in front of it. The root-level location without the .well-known directory is a legacy fallback and should not be relied on for new setups.
Does the apple-app-site-association file need a .json extension?
No. The filename is matched literally and has no extension. Adding .json creates a different URL that iOS will not request, which produces a 404 and silently disables Universal Links for the domain.
Why are my Universal Links not working after I updated the AASA file?
Two caches sit between the edit and the device. Apple's CDN refreshes its copy on its own schedule, which can take up to 24 hours, and each device caches the association it received when the app was installed. Deleting and reinstalling the app forces the device to re-fetch, and adding ?mode=developer to the entitlement on a development build bypasses the CDN entirely.
What is the difference between appID and appIDs in an AASA file?
appID is the legacy key from iOS 9 through 12 and takes a single string alongside a paths array. appIDs is the modern key introduced in iOS 13, takes an array so several apps can share one entry, and is used with components instead of paths. Components can match query parameters and URL fragments, which paths cannot. Both keys can be present in the same file for backwards compatibility.
Does the AASA file need to be signed?
No. Signing the file with a CMS certificate was required before iOS 9.3 and has not been required since — a current apple-app-site-association file is plain, unsigned JSON served with a Content-Type of application/json. Guides that tell you to run openssl smime over the file predate that change and will produce a file iOS cannot parse.
How large can an apple-app-site-association file be?
128 KB. Apple enforces this as a hard limit, and a file that exceeds it is ignored in full rather than truncated, so every Universal Link on the domain stops working at once. Large path lists are the usual cause; wildcards in components are far more compact than enumerated paths.

Related terms

  • AASA file not foundAn 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.
  • Android App Links not workingAndroid App Links fail when the system cannot verify that a domain and an app belong to the same owner, at which point tapped links open in the browser instead of the app with no error shown to the user.