Skip to content
Tolinku
Tolinku
Sign In Start Free
iOS Development · · 9 min read

Apple App Site Association File: Complete Setup Guide

By Tolinku Staff
|
Tolinku universal links dashboard screenshot for ios blog posts

The Apple App Site Association file is the foundation of Universal Links on iOS. Without it, tapping a link to your domain opens Safari instead of your app. Get it right once, and iOS will route your links directly into the app experience you intended. Get it wrong, and you spend hours debugging a silent failure.

This guide covers the complete setup: file format, JSON structure, path matching rules, serving requirements, and how to verify everything is working. If you are new to Universal Links in general, start with our complete guide to Universal Links for the broader picture before diving into AASA specifics.

What the AASA File Does

When a user installs your app, iOS fetches the AASA file from your domain and caches it. Later, when the user taps a link to your domain, iOS checks that cached file to determine whether your app should handle the URL. If the path matches a rule in the file, iOS opens the app. If it does not match, or if the file was never fetched successfully, the link opens in Safari.

This lookup happens at install time (and periodically thereafter via Apple's CDN), not at tap time. That distinction matters for troubleshooting: if you update your AASA file, users with the app already installed will not see the change immediately. Apple's servers re-crawl the file on their own schedule.

The file must live at one of two locations on your domain:

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

Apple always checks the .well-known/ path first. The root path is the fallback for older iOS versions. You only need one, but serving both causes no harm. Either way, the file must be at the root of the domain (or subdomain) you register in your app's entitlements. You cannot nest it under a subdirectory.

JSON Structure

The AASA file is a JSON document with no file extension. Here is the minimal structure for Universal Links:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["TEAMID.com.example.myapp"],
        "components": [
          {
            "/": "/shop/*",
            "comment": "Match all shop URLs"
          }
        ]
      }
    ]
  }
}

A few things to note about this structure:

The appIDs field takes an array of strings in the format TEAMID.bundleID. Your Team ID is the 10-character alphanumeric string found in the Apple Developer portal under Membership Details. Your Bundle ID is the identifier you set in Xcode (for example, com.example.myapp). The combined value looks like A1B2C3D4E5.com.example.myapp.

The details array can contain multiple objects, each targeting different app IDs with different path rules. This is how you support multiple apps or multiple build targets (production, staging, beta) from a single AASA file.

The components array is the modern syntax introduced in iOS 13. Apple still supports the older paths syntax but recommends components for new implementations. The two are not interchangeable within a single entry.

App ID Format

The App ID format trips up a lot of developers. It is always TEAMID.bundleID, never just the bundle ID. Two common mistakes:

  1. Using the App ID prefix from the App IDs list in the developer portal instead of the actual Team ID. These can look similar but are not always the same value.
  2. Confusing the Team ID with the App ID itself. In the developer portal, navigate to Membership, not to Identifiers, to find the correct Team ID.

You can verify your Team ID with a quick check:

# From your provisioning profile
security cms -D -i ~/Library/MobileDevice/Provisioning\ Profiles/your-profile.mobileprovision | grep -A1 TeamIdentifier

Path Matching with Components

The components syntax gives you fine-grained control over which URLs trigger your app. Each object in the components array can have these keys:

  • / : the path pattern to match
  • ? : a query string pattern to match
  • # : a fragment pattern to match
  • exclude : set to true to exclude matching URLs (exclusions are evaluated in order)
  • comment : a human-readable label (ignored by iOS)

Wildcards

The * character matches any sequence of characters. The ? character (when used inside a path pattern, not as the query key) matches any single character. For a deeper look at wildcard behavior, edge cases, and advanced pattern strategies, see our guide to AASA wildcards and path matching.

{
  "components": [
    {
      "/": "/products/*",
      "comment": "All product detail pages"
    },
    {
      "/": "/u/*/settings",
      "comment": "User settings pages"
    }
  ]
}

Exclusions

Exclusions let you carve out paths that should open in Safari even when they fall under a broader match. List exclusions before the broader rule they override:

{
  "components": [
    {
      "/": "/products/archived/*",
      "exclude": true,
      "comment": "Archived products open in browser"
    },
    {
      "/": "/products/*",
      "comment": "All other product pages open in app"
    }
  ]
}

Order matters here. iOS evaluates components entries from top to bottom and stops at the first match. If you put the wildcard /products/* before the exclusion, the exclusion will never be reached.

Query String Matching

You can require specific query parameters as part of a match:

{
  "components": [
    {
      "/": "/share",
      "?": { "ref": "?" },
      "comment": "Share URLs with any ref parameter"
    }
  ]
}

The ? value in the query object means "any single character or more." You can also match exact values or use * for longer strings.

Matching Everything

To route all paths on a domain to your app, use a single wildcard:

{
  "components": [
    {
      "/": "*"
    }
  ]
}

Be cautious with this. It means every link to your domain, including password reset emails and marketing unsubscribe pages, will try to open the app. A more targeted approach is usually better.

Multiple Apps in One File

If you have multiple apps that should handle links from the same domain, list them all in the details array:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["A1B2C3D4E5.com.example.consumer"],
        "components": [
          { "/": "/shop/*" },
          { "/": "/account/*" }
        ]
      },
      {
        "appIDs": ["A1B2C3D4E5.com.example.driver"],
        "components": [
          { "/": "/driver/*" }
        ]
      },
      {
        "appIDs": [
          "A1B2C3D4E5.com.example.consumer.beta",
          "A1B2C3D4E5.com.example.consumer.staging"
        ],
        "components": [
          { "/": "*" }
        ]
      }
    ]
  }
}

The last entry in this example covers both a beta and staging build with a single rule by listing both app IDs in the appIDs array.

Serving Requirements

Apple's crawler has strict requirements for how the AASA file must be served. Any deviation causes a silent failure.

HTTPS only. The file must be served over HTTPS with a valid certificate. Self-signed certificates are not accepted. This applies to your production domain and any subdomain listed in your app's Associated Domains entitlement.

No redirects. Apple's crawler does not follow redirects. If your server redirects /.well-known/apple-app-site-association to another URL (even just adding a trailing slash), the crawl fails. Check your web server configuration, CDN rules, and any middleware that might issue automatic redirects.

Correct content type. Serve the file with Content-Type: application/json. Some servers default to application/octet-stream for files with no extension. You will need to explicitly set the content type.

For Nginx:

location = /.well-known/apple-app-site-association {
    default_type application/json;
}

For Apache:

<Files "apple-app-site-association">
    ForceType application/json
</Files>

File size limit. The AASA file must be 128 KB or smaller. In practice, even large files with hundreds of path rules stay well under this limit.

No authentication. The file must be publicly accessible. No HTTP basic auth, no IP restrictions.

Xcode Entitlements

The AASA file alone is not enough. Your app must also declare the associated domain in its entitlements. In Xcode, go to your target's Signing and Capabilities tab, click the "+" to add a capability, and select Associated Domains. Add an entry in the format:

applinks:yourdomain.com

If you want to handle links on a subdomain, add that subdomain separately:

applinks:app.yourdomain.com

The entitlement and the AASA file must agree. If your entitlement points to app.yourdomain.com, Apple will look for the AASA file at https://app.yourdomain.com/.well-known/apple-app-site-association, not at your root domain. For a full walkthrough of the entitlement setup process, including provisioning profiles and development mode, see our Associated Domains entitlement guide.

For more on wiring up the Swift side of Universal Links, including the application(_:continue:restorationHandler:) delegate method and SwiftUI equivalents, see the Tolinku iOS setup guide.

Validation

There are several ways to verify your setup before shipping to users.

Apple's Validator

Apple provides an AASA validator at… actually, use the official tool at developer.apple.com or check via aasa-validator CLI tools. The most reliable official check is the App Search API validation tool in the Search tab of App Store Connect for indexed content, but for basic AASA validation, a direct curl check is often faster.

curl Check

The fastest way to verify your file is reachable and correctly configured:

# Check the file is reachable with the right content type
curl -sI https://yourdomain.com/.well-known/apple-app-site-association

# Fetch and pretty-print the file contents
curl -s https://yourdomain.com/.well-known/apple-app-site-association | python3 -m json.tool

# Check for redirects (should show no Location header)
curl -sIL https://yourdomain.com/.well-known/apple-app-site-association | grep -E "HTTP/|Location:"

Look for HTTP/2 200 (or HTTP/1.1 200) and content-type: application/json in the response headers. Any Location: header indicates a redirect that will break Apple's crawler.

Apple's CDN

From iOS 14 onward, Apple routes AASA requests through its own CDN rather than having each device fetch your file directly. This means your file may be cached at Apple's edge and could lag behind your latest changes by hours. We cover this caching behavior in detail in our article on Apple CDN validation and Universal Links. To force a re-fetch during development, you can delete and reinstall the app, or use the applinks:yourdomain.com?mode=developer entitlement value in debug builds. This developer mode bypasses Apple's CDN and fetches directly from your server.

applinks:yourdomain.com?mode=developer

Note: ?mode=developer only works for apps installed via Xcode or TestFlight on devices registered to your team.

On-Device Verification

After installing a debug build via Xcode, open the Console app on your Mac and filter for the process swcd (the service responsible for associated domains). You will see log entries showing whether Apple successfully fetched and parsed your AASA file, along with any errors.

Common Mistakes

Wrong App ID format. The single most common error. Double-check that you are using your Team ID, not your App ID prefix.

Redirect on the AASA URL. Even a harmless-looking redirect like HTTP to HTTPS, or a trailing slash added by the server, will cause the crawl to fail. Test with curl and watch for any redirect.

File served with wrong content type. Servers often default to application/octet-stream for files without extensions. Explicitly set application/json.

Entitlement and file domain mismatch. If the app entitlement says applinks:app.yourdomain.com but the AASA file is only at yourdomain.com, the link will not work.

Old paths syntax mixed with components. Apple's documentation deprecated the older paths array in favor of components. Do not mix them in the same entry.

Assuming changes propagate immediately. Apple caches the AASA file. Test with developer mode enabled to see your latest changes without waiting for the cache to expire. For a systematic approach to tracking down these and other issues, see our AASA debugging guide.

Missing the webcredentials or activitycontinuation sections. If you also need Shared Web Credentials or Handoff, those are separate top-level keys in the same AASA file. Omitting them when you need them is easy to overlook.

Putting It Together

Here is a complete, production-ready AASA file for a typical e-commerce app:

{
  "applinks": {
    "details": [
      {
        "appIDs": ["A1B2C3D4E5.com.example.shop"],
        "components": [
          {
            "/": "/checkout/*",
            "exclude": true,
            "comment": "Checkout handled in browser for payment security"
          },
          {
            "/": "/products/*",
            "comment": "Product detail pages"
          },
          {
            "/": "/collections/*",
            "comment": "Category and collection pages"
          },
          {
            "/": "/account/*",
            "comment": "Account and order history"
          },
          {
            "/": "/share/*",
            "comment": "Shared content"
          }
        ]
      }
    ]
  }
}

Once the file is in place, your server is configured to serve it correctly, and the entitlement is added in Xcode, Universal Links will work for every new install. For existing installs, the update propagates as Apple's CDN re-crawls your domain.

For a broader look at how Universal Links fit into a deep linking strategy, including deferred links and analytics, see Tolinku's Universal Links documentation. If you want to see how path matching and route configuration work in a managed setup, the deep linking features overview covers how Tolinku handles AASA generation and hosting automatically.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.