ARLing

Notes

Universal links open Safari instead of your Expo app: AASA, assetlinks and app.json checklist

Andrej Lauko, ARLing · 6 September 2026

You add associatedDomains or intentFilters to app.json, build the app, and tap a https://yourapp.com/... link from Messages, Notes, or another app to test it. Instead of opening your Expo app, the link opens in Safari on iOS or in Chrome on Android, with no error anywhere: no crash, no console warning. On Android, adb shell pm get-app-links <package> often gives the only clue, showing legacy_failure under "Domain verification state" instead of verified. Universal Links and App Links are not deep links: iOS and Android will only hand the app a https:// link after independently verifying, at install time, that you actually own the domain. That verification has several separate places to get wrong, and each one fails silently.

1. You're testing in Expo Go

Universal Links and App Links are checked against the installed app's bundle ID and signing key. Expo Go is itself the installed app, so it can never claim your bundle ID. Expo's own linking guide says it plainly: "Support for incoming links in Expo Go is limited. We recommend using Development builds to test your app's linking strategies." Tapping the link in Expo Go just opens the browser, no matter how correct your AASA file and app.json are.

Fix: test with a development build (npx expo run:ios, npx expo run:android, or an EAS dev-client build), or a standalone/production build.

2. iOS associatedDomains has the wrong shape

In app.json, expo.ios.associatedDomains entries must be exactly applinks:<domain>: no https:// prefix, no path after the domain. Expo's docs call the protocol prefix out directly: "Make sure to follow Apple's specified format and not include the protocol (https) in your URL. This is a common mistake that will result in the universal links not working." An entry like https://example.com or applinks:example.com/app is silently ignored by iOS.

{
  "expo": {
    "ios": {
      "associatedDomains": ["applinks:example.com"]
    }
  }
}

3. The apple-app-site-association file doesn't match, or is redirected

The AASA file hosted at /.well-known/apple-app-site-association needs an applinks.details[] entry whose appID (or appIDs) is exactly <Apple Team ID>.<Bundle Identifier>, for example ABCDE12345.com.example.app. iOS requires an exact string match: one typo and the app is never offered the link. Two hosting mistakes cause the same silent failure. First, a 301 or 302 redirect on the AASA URL: Apple's own debugging guide is explicit that a "HTTP redirect... is not supported when hosting the AASA file," so a domain that forwards to another domain must host its own copy instead. Second, the file must be served over HTTPS with Content-Type: application/json and no BOM at the start.

{
  "applinks": {
    "apps": [],
    "details": [
      { "appID": "ABCDE12345.com.example.app", "paths": ["/product/*"] }
    ]
  }
}

4. assetlinks.json has the wrong shape, or the wrong fingerprint

assetlinks.json at /.well-known/assetlinks.json must be a JSON array, even for one app: a bare object at the top level fails verification. Each entry needs relation: ["delegate_permission/common.handle_all_urls"] and target.namespace: "android_app". The most common reason a config that looks correct still fails on-device: the listed sha256_cert_fingerprints come from a local debug keystore, not from the actual EAS build. Get the right one with eas credentials -p android, selecting the build profile you're testing, then "SHA256 Fingerprint".

[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.myapp",
      "sha256_cert_fingerprints": ["14:6D:E9:..."]
    }
  }
]

5. Android intentFilters is missing autoVerify

Without autoVerify: true on the intent-filter, Android treats the link as an ordinary, unverified filter: the browser's "Open with" chooser wins instead of your app opening directly. Expo's Android linking guide states it as a requirement, not an option: "Specifying autoVerify is required for Android App Links to work correctly." The filter also needs scheme: "https" and both the BROWSABLE and DEFAULT categories.

{
  "expo": {
    "android": {
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [{ "scheme": "https", "host": "example.com", "pathPrefix": "/product" }],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

6. The path pattern doesn't actually cover the link you tested

Both platforms match paths differently than most people expect. In the AASA file's paths or components, * matches a run of characters but never crosses a / or .: per Apple's TN3155 guide, /product/* matches /product/42 but not /product/42/reviews. On Android, an intent-filter's pathPrefix is a literal string prefix, not a wildcard pattern, so pathPrefix: "/product/42" won't cover /product/43. A link that worked for one test path can fail for the next one for this reason alone.

Fix: pick a pattern that actually starts with, or matches, every path you intend to open, not just the one you tested first.

Checklist

  1. Confirm you tested on a development or standalone build, never Expo Go.
  2. curl -I https://yourdomain/.well-known/apple-app-site-association: expect HTTP 200, no redirect, Content-Type: application/json.
  3. curl https://yourdomain/.well-known/assetlinks.json: valid JSON array, correct package_name and fingerprint.
  4. Reinstall the app after changing the AASA file. Apple's CDN fetches and caches it per device at install time, so an edit doesn't reach an already-installed app on its own.
  5. adb shell pm get-app-links <package>: look for verified under "Domain verification state," not legacy_failure or none.
  6. On a Mac: swcutil dl -d <domain> shows what Apple's CDN actually cached; swcutil verify -d <domain> -j <file> -u <url> checks one URL against it.

Check it in 30 seconds

Paste your app.json, apple-app-site-association and assetlinks.json into the Expo Universal Links Doctor and it points at the exact mismatch.

Sources

  1. Expo, iOS Universal Links: docs.expo.dev/linking/ios-universal-links
  2. Expo, Android App Links: docs.expo.dev/linking/android-app-links
  3. Expo, Linking overview (Expo Go limitation): docs.expo.dev/linking/overview
  4. Apple, Supporting associated domains: developer.apple.com/documentation/xcode/supporting-associated-domains
  5. Apple, TN3155: Debugging universal links: developer.apple.com/documentation/technotes/tn3155-debugging-universal-links
  6. Android, Verify Android App Links: developer.android.com/training/app-links/verify-android-applinks

Andrej Lauko, ARLing · 6 September 2026