In a real app
With Expo, ios.associatedDomains (["applinks:example.com"], no protocol, the most frequent mistake) and android.intentFilters (with autoVerify: true) in app.json cover the app side; EAS Build registers the entitlement with Apple. Without EAS or prebuild, you enable the Associated Domains capability in Xcode and the com.apple.developer.associated-domains entitlement. Android fingerprints come from eas credentials -p android or the Play Console (App Signing), and you need one per signing variant: debug, local release, Play App Signing.
The iOS pitfall is caching: the system downloads the AASA at install or update time, through Apple’s CDN, and does not refresh it often. Changing paths in production therefore needs a new App Store version, and an AASA broken on launch day stays broken for the users who installed that day. The file must be under 128 KB uncompressed and the * wildcard does not cross dots or slashes. On Android, verification runs after install and can take twenty seconds or more; adb shell pm get-app-links <package> shows its state.
I check both files with curl -I (200, no redirect, correct content type), that the declared paths match the actual Expo Router routes, and that a fallback web page exists for every path, because a universal link without a web page is a dead link for everyone without the app.
Related terms
Deep linking
Opening a specific screen of the app from a URL: a custom scheme (myapp://) or a verified https link (universal links, App Links).
Expo Router
Expo’s file-based router: every file in the app directory becomes a screen, with deep links and typed routes out of the box.
EAS Build
Expo’s hosted build service: profiles in eas.json, credentials management, and macOS and Linux runners for iOS and Android.
Config plugin
A JavaScript function declared in app.json that modifies the native projects prebuild generates (Info.plist, manifest, Gradle, Podfile).