How to Add Deep Links and Universal Links to an Expo App

Deep linking lets a URL open a specific screen inside your mobile app — powering everything from push notification taps and marketing campaigns to password-reset flows and social sharing. Without it, links either fail silently or drop users on a generic home screen. Getting this right in Expo involves three distinct mechanisms, and choosing the wrong one is the most common source of wasted effort.

This guide covers all three approaches: custom URL schemes for simple cases, iOS Universal Links using Apple’s AASA verification file, and Android App Links using Google’s assetlinks.json. If you are using Expo Router, the routing side is already handled automatically — you mainly need to wire up the native configuration and host the right files on your domain.

Photo: Challiyan / CC BY-SA 4.0, via Wikimedia Commons

Quick Answer

Add a `scheme` field to your app.json for basic custom-scheme deep links (e.g., `myapp://`). For HTTPS-based Universal Links on iOS and App Links on Android — which work even without the app installed and are required for production marketing links — you also need to add `associatedDomains` (iOS) and `intentFilters` (Android) to app.json, then host a verification file at `yourdomain.com/.well-known/apple-app-site-association` (iOS) and `yourdomain.com/.well-known/assetlinks.json` (Android). Rebuild your app with EAS Build after any config change.

Step 1 — Add a Custom URL Scheme

A custom URL scheme (e.g., `myapp://products/42`) is the simplest form of deep link. Open your `app.json` and add a top-level `scheme` field inside the `expo` object: `”scheme”: “myapp”`. If you are using Expo Router, also ensure `expo-router` is listed in the `plugins` array. After saving, run `npx expo prebuild –clean` followed by `npx expo run:ios` or `npx expo run:android` to rebuild with the new native config.

To test on an iOS simulator, run `xcrun simctl openurl booted “myapp:///products/42″`. On Android, use `adb shell am start -W -a android.intent.action.VIEW -d “myapp:///products/42” com.yourcompany.myapp`. To handle the incoming URL in code without Expo Router, call `Linking.getInitialURL()` for cold-start launches and attach a `Linking.addEventListener(‘url’, callback)` listener for links received while the app is running. With Expo Router you can skip both — routing is automatic.

Step 2 — Configure iOS Universal Links

Universal Links use regular HTTPS URLs, so they work in iMessage, email clients, and web browsers. If the app is not installed, the URL falls back to your website. First, add `associatedDomains` to the `ios` section of your app.json: `”associatedDomains”: [“applinks:yourapp.com”, “applinks:www.yourapp.com”]`. Do not include `https://` — Apple requires just the bare domain. Then rebuild using `npx eas build –platform ios` so EAS automatically registers the Associated Domains entitlement with Apple.

Next, create the Apple App Site Association (AASA) file and place it at `public/.well-known/apple-app-site-association` (Expo Router projects) or `web/.well-known/apple-app-site-association` (legacy webpack). The file must be valid JSON with no `.json` extension and served over HTTPS with `Content-Type: application/json`. A minimal AASA using the modern components format looks like this: `{ “applinks”: { “details”: [{ “appIDs”: [“TEAMID.com.yourcompany.myapp”], “components”: [{ “/”: “*”, “comment”: “Matches all paths” }] }] } }`. Replace `TEAMID` with your 10-character Apple Team ID (found in the Apple Developer portal) and use the exact bundle identifier from your app.json. In the components array, the `”/”` key (a literal forward-slash string) specifies the URL path pattern, `”#”` matches URL fragments, and `”?”` matches query parameters. Verify the file is reachable with `curl -I https://yourapp.com/.well-known/apple-app-site-association`. Test Universal Links on a physical device only — iOS simulators do not verify AASA files.

Photo: Katharva / CC BY-SA 3.0, via Wikimedia Commons

Step 3 — Configure Android App Links

Android App Links work similarly to Universal Links but use Google’s Digital Asset Links protocol. In app.json, add an `intentFilters` array under `expo.android`: set `action` to `VIEW`, `autoVerify` to `true`, `category` to `[“BROWSABLE”, “DEFAULT”]`, and `data` to `{ “scheme”: “https”, “host”: “yourapp.com”, “pathPrefix”: “/” }`. The `autoVerify: true` flag tells Android to verify ownership of the domain at install time. Rebuild the app after this change.

Create `public/.well-known/assetlinks.json` and host it over HTTPS. The content should be: `[{ “relation”: [“delegate_permission/common.handle_all_urls”], “target”: { “namespace”: “android_app”, “package_name”: “com.yourcompany.myapp”, “sha256_cert_fingerprints”: [“AA:BB:CC:…”] } }]`. Get your SHA-256 fingerprint by running `eas credentials -p android` and selecting your build profile — look for the SHA256 Fingerprint field. If you use Google Play App Signing, include both the upload key fingerprint and the Google-managed production key fingerprint in the array. Verify Android verification status on a device with `adb shell pm get-app-links com.yourcompany.myapp`.

Tips and Common Mistakes

Do not include `https://` in the `associatedDomains` value for iOS — Apple’s format requires just the bare domain. Forgetting this causes silent failures that are hard to debug. In your AASA file, each entry in the `components` array must use `”/”` (a literal forward-slash string) as the key for path patterns — using any other string such as `”path”` will cause Apple to reject or ignore the file entirely. Make sure the bundle identifier in your AASA file exactly matches the one in app.json — a single character difference breaks verification. On Android, if you use Google Play App Signing (enabled by default for new Play Console apps), you must add the Google-managed certificate fingerprint to assetlinks.json in addition to your upload key fingerprint; find it in the Play Console under Setup → App integrity. AASA file changes can take up to 24 hours to propagate on CDN after first deploy, and Apple caches the file aggressively — some devices may not pick up changes for several days. Finally, note that Firebase Dynamic Links shut down in August 2025. If your app still references `.page.link` domains, those URLs now return 404 errors — migrate to a service like Branch, Adjust, or AppsFlyer for deferred deep linking.

Explore more: More App Development guides.

Expo deep links and universal links FAQs

What is the difference between a custom URL scheme and a Universal Link?

A custom URL scheme (like `myapp://`) only works if the app is already installed — if not, the OS shows an error. A Universal Link (iOS) or App Link (Android) uses a regular HTTPS URL that falls back to your website if the app is not installed. Universal Links are preferred for marketing and sharing because they work seamlessly in any context.

Do I need to rebuild my Expo app after changing the linking configuration?

Yes. Changes to app.json fields like `scheme`, `associatedDomains`, or `intentFilters` are native configuration changes that require a new native build. Run `npx eas build` for a production build or `npx expo prebuild –clean && npx expo run:ios` for a local development build to apply the changes.

Can I test Universal Links without deploying to a real server?

During development, Expo CLI’s `–tunnel` flag (via `npx expo start –tunnel`) forwards your local server to a public HTTPS URL, which you can temporarily use as your domain for AASA hosting. For a more stable setup, deploy the `.well-known` directory to any static hosting service (Netlify, Vercel, or EAS Hosting) and point your `associatedDomains` at that domain.

Build It With GTStudios

Need help shipping your app, game, or small-business tech? GTStudios builds web, apps, and games. See how GTStudios can help.

Photo: Kolforn (Kolforn) I’d appreciate if you could mail me (Kolforn@gmail.com) if you want to use this picture out of the Wikimedia project scope. This file is licensed under the Creative Commons Attribution-Share Alike 4.0 International license. You are free: to share – to copy, distribute and transmit the work to remix – to adapt the work Under the following conditions: attribution – You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. share alike – If you remix, transform, or build upon the material, you must distribute your contributions under the same or compatible license as the original.https://creativecommons.org/licenses/by-sa/4.0CC BY-SA 4.0 Creative Commons Attribution-Share Alike 4.0 truetrue / CC BY-SA 4.0, via Wikimedia Commons.