How do you set up Universal Links and Android App Links?

Learn what goes in apple-app-site-association and assetlinks.json, where to host them, how to test them, and why links can still fail.

An abstract illustration for an article about Setting Up and apple-app-site-association and assetlinks.jso.
AI-generatedSetting Up and apple-app-site-association and assetlinks.jso

Universal Links on iOS and App Links on Android let a tapped link to your domain open your app instead of a browser. Both need proof that the domain in the link belongs with the app. That proof is the apple-app-site-association file for iOS and the assetlinks.json file for Android. You publish both on your live site and declare the same domain inside the app.

  • The iOS file lives at /.well-known/apple-app-site-association with no file extension.
  • The Android file is a JSON array of statements served from /.well-known/assetlinks.json.
  • Both files need HTTPS, an HTTP 200 response, the application/json content type and no redirects.
  • Existing users only get the feature after you release a new app version to the store.

What does the apple-app-site-association file contain?

The apple-app-site-association file is served from /.well-known/apple-app-site-association. It has no file extension, but its contents are JSON.

Inside, the applinks object can hold three keys: defaults, details and substitutionVariables. The details array lists the apps and the universal links each one handles for the domain. Each app identifier is your Team ID followed by your Bundle ID, for example ABCDE12345.com.example.app.

You can also keep some URLs out of the app. A component entry with "exclude": true tells the system not to open matching URLs as universal links. Older-style paths entries use a NOT prefix for the same purpose. A /* catch-all opens any URL from the domain in the app, except the paths your NOT rules exclude.

What goes in assetlinks.json?

The assetlinks.json file is a JSON array of statements. Each statement has a relation of delegate_permission/common.handle_all_urls and a target. The target holds the namespace android_app, your package_name and your sha256_cert_fingerprints.

The handle_all_urls relation works like the iOS catch-all, so Android opens URLs from the domain in your app. The fingerprint must come from the certificate you sign the app with. Even a debug build should be signed, or you will see the app selection dialog when you test.

Both files follow the same hosting rules

Serve each file over HTTPS on the exact domain your links use, including www. where that applies. The response should return HTTP 200 with Content-Type application/json. Avoid redirects entirely.

On at least one commerce platform, Universal Links and App Links do not work on the default store subdomain. There, they require a custom domain with HTTPS enabled.

The app must declare the domain too

On iOS, the app needs the Associated Domains capability. Each domain goes into the entitlements file as applinks: followed by the domain name.

On Android, add an intent filter for the domain in AndroidManifest.xml and handle incoming intents in your activity. Because both changes live inside the app, you must rebuild and publish a new version before existing users get the feature.

How does Android check your domain?

Add android:autoVerify="true" to at least one intent filter. The system then verifies the hosts in your intent filters when the app is installed on Android 6.0 (API 23) or higher. It inspects filters with the action android.intent.action.VIEW, the categories BROWSABLE and DEFAULT, and an http or https scheme.

For each unique hostname, Android fetches the file from /.well-known/assetlinks.json on that host. The check runs in the background, so wait at least 20 seconds after installing before you look at the result.

Starting in Android 12, you can run the check by hand with adb. Use pm set-app-links to reset, pm verify-app-links --re-verify to check again and pm get-app-links to review the results. A domain that passes shows the state 'verified'.

How do you test that links open the app?

First, fetch each file with curl from /.well-known/ on your domain. If that request fails or redirects, the device will fail too.

TaskiOSAndroid
Open a test linkxcrun simctl openurl booted <url>adb shell am start -a android.intent.action.VIEW -d <url>
Check domain statusSettings > Developer > Universal Links > Diagnostics on a physical deviceApp Links Assistant in Android Studio

Tap the link rather than typing it. Pasting a URL into a browser address bar does not launch the app.

Why do links still open in the browser?

The most common cause is link wrapping. External shorteners and email click tracking change the domain, so the system no longer sees a domain tied to your app.

Device makers matter as well. Samsung's OS cannot open apps using Android App Links, so URI schemes are the primary way to open apps on Samsung devices.

If verification fails, users can still link the app to the domain by hand in system settings under 'Open by default'. Your app can take them there with the ACTION_APP_OPEN_BY_DEFAULT_SETTINGS intent.

What happens when you change the files later?

iOS re-fetches the association files from time to time. Android behaves differently depending on the version.

On Android 15 (API 35) and higher, the system re-checks domains in the background, and changes to assetlinks.json can take up to seven days to reach all devices. On Android 14 and lower there is no periodic re-check. Updates are usually picked up only when the app is installed or updated, so plan your file changes alongside your next release.