Mobile app deep linking works when two files are in the right place.
A link either opens your app or opens a web page, and users notice which one happened. Mobile app deep linking is the machinery that decides, and on both platforms the decision turns on a small file you host on your own domain. Apple calls its version Universal Links. Google calls its version Android App Links. Neither platform takes your word for the association: both go and read your web server. If you are earlier than this in the process, start with the app development guide.
This page gives the exact file names and paths Apple and Google publish, the serving rules that break them quietly, why a correctly configured link still opens the browser, and what deferred deep linking can honestly do after an install.
Check the two files for your domainThe short version
One file for Apple, one for Google, both in the .well-known directory.
Apple wants a file named apple-app-site-association, with no extension, served at https://yourdomain.com/.well-known/apple-app-site-association. Google wants a file named assetlinks.json, served at https://yourdomain.com/.well-known/assetlinks.json. Both have to come back over HTTPS, and both have to come back without a redirect.
The app side of the work, one entitlement on iOS and one intent filter on Android, is a short edit. The file is where projects lose a day, because a broken file fails silently. There is no error. The link just opens the web page, and everyone assumes the app is fine.
What Apple looks for, and where
On iOS the association file is named apple-app-site-association, and Apple states plainly that it has no extension. Not .json, no extension at all. It goes in your site's .well-known directory, so the URL reads https://yourdomain.com/.well-known/apple-app-site-association. Apple also requires that you host the file using https with a valid certificate and with no redirects.
Inside, the file holds an applinks object with a details array. Each entry lists appIDs in the form of your Team ID prefix followed by your bundle identifier, such as ABCDE12345.com.example.app, plus a components array saying which paths on the domain belong to the app. That Team ID prefix is the same identifier you handle when you automate releases through the app store connect api. On the app side you add the Associated Domains capability with an entry shaped like applinks:example.com, with no path, no query and no trailing slash. Each subdomain needs its own entry and serves its own file.
One detail changes how you debug all of this. Since macOS 11 and iOS 14, apps no longer request the file from your web server directly. The request goes to an Apple managed content delivery network instead. Apple documents that its CDN requests the file for your domain within 24 hours, and that devices check for updates roughly once a week after the app is installed. So fixing the file and seeing the fix are two different events. If your web server is not reachable from the public internet while you develop, Apple documents an alternate mode that you turn on by adding a query string to the entitlement, which bypasses the CDN.
Apple, Supporting associated domains
Try it
The two URLs that have to work
Put in your domain, then open both of these in a private browser window. Switch off any rule your server breaks.
https://example.com/.well-known/apple-app-site-association
https://example.com/.well-known/assetlinks.json
Both files are served the way Apple and Google ask for. Verification has what it needs.
What Google checks on the Android side
The Android file is assetlinks.json, and Google gives the location as https://domain.name/.well-known/assetlinks.json. For each unique hostname found in your intent filters, Android queries that address. Three serving rules travel with it. The file is served with content type application/json. It has to be reachable over an HTTPS connection, whether or not your intent filters declare https as the data scheme. And it has to be reachable without any redirects, no 301 and no 302.
The contents are a relation and a target. The relation is delegate_permission/common.handle_all_urls. The target names the android_app namespace, your package_name, and sha256_cert_fingerprints. The fingerprint is where most failures live: if Google Play signs your app, the fingerprint that matters is the one Play signs with, not the upload key on your laptop. One file can list several apps, and if your links cover several host domains you publish the file on every one of them.
In the manifest, at least one intent filter needs android:autoVerify set to true, the android.intent.action.VIEW action, both the BROWSABLE and DEFAULT categories, and an http or https data scheme. The system then verifies the association. Get the fingerprint wrong and the app installs perfectly, runs perfectly, and simply never claims the link. Nothing in the build tells you.
Why the link still opens the browser
Most taps do not start in Safari or Chrome. They start inside Instagram, Gmail, Slack or a mail client, and many of those apps open links in an embedded web view of their own. An embedded web view renders a web page. It is not a handoff, so your association is never consulted. This is the most common reason a link that is configured correctly still opens the web, and no amount of fixing the file will change it.
Redirects are the second reason, twice over. Both platforms refuse a redirect on the association file itself, so a tidy rule on your web server that forces every path through a canonical host can break verification without touching a line of app code. And the link itself is matched against the URL the user actually opened. A shortened link belongs to the shortener, so the association on your domain is not in the conversation. Serve deep links on a host you control.
The rest are quieter. The user can opt out: iOS remembers when someone chooses to open your domain in the browser, and Android lets a user turn off verified links for an app in system settings. Hosts have to match exactly, so example.com and www.example.com are two hosts, two entries and two files. And a staging domain with basic auth in front of it returns a login page rather than JSON, which reads as a missing file. Open both URLs in a private window before you rewrite any code, and test on a device that has never opened the link, because your own phone is contaminated by every earlier attempt.
Deferred deep linking, and where it stops
The tap you care about most is the one from a person who does not have the app. They tap, land in the store, install, open, and see a generic home screen with no memory of why they came. Deferred deep linking is the name for carrying that original intent across the install, and the two platforms are not equal here.
Android has a first party answer. Google Play passes an install referrer string, and the Play Install Referrer library hands your app that string on first run along with the referrer click and install begin timestamps. Google documents the referrer as available for 90 days, unchanged unless the app is reinstalled, and advises calling the API once during the first run after install. That is enough to route a new user to the right screen with no third party in the path.
iOS has no equivalent that carries a link through the App Store. Third party attribution SDKs fill the gap with probabilistic matching on device signals or with clipboard reads, and both routes have narrowed: the system now shows the user an alert when an app reads the pasteboard, and matching a person across apps by device signals is the behaviour governed by app tracking transparency. If the feature has to exist, budget for an SDK and read the consent rules before you design the flow around it.
There is a duller pattern that works on both platforms. Ask the new user for the thing the link was about: a short code shown on the page they came from, or a sign in that restores their context from your own server. It is one extra screen, and it survives every privacy change either platform makes.
What each kind of link actually gives you
| Approach | Opens the installed app | File needed on your domain | If the app is missing | After install, lands on the content |
|---|---|---|---|---|
| Custom URL scheme such as myapp | Yes | No | nothing happens or an error page | No |
| Plain https link, no association | No | No | opens the web page | No |
| Universal Links on iOS | Yes | apple-app-site-association | opens the web page | No |
| Android App Links | Yes | assetlinks.json | opens the web page | No |
| App Links plus Play install referrer | Yes | assetlinks.json | opens the web page | on Android, yes |
Building the link handling yourself
Hosted linking services do real work: they hold the association files, give you a dashboard, and paper over the deferred case on iOS. What you are also buying is a middleman on your most important links, with pricing and uptime you do not control. Owning the domain and the two files costs an afternoon and cannot be taken away from you.
Newly is an AI app builder. You describe the app in plain English and it writes a real React Native and Expo project you own, runs it on a cloud iPhone or Android simulator while it builds, and uploads iOS builds to TestFlight using your own Apple Developer account. The Deploy tab has an Android section: one press builds, signs and uploads the app to Google Play internal testing, and it also builds a standalone release APK you can download and install on a phone directly. Plans are $25 a month and there is no free plan. Link handling lives in the project itself, and you can pull the code with npm i -g @newly/cli and then newly pull, so the entitlement and the intent filter are yours to edit. What no builder can do for you is put the file on your domain. That half belongs to whoever runs your web server.
The other place a URL has to decide which screen to show is a notification payload. Same routing problem, different delivery path, and it is worth reading about opening the right screen from a notification before you build the router once for links and again for pushes.
Questions people ask about deep linking
A deep link is a URL that opens a specific screen inside an app rather than the app's home screen or a web page. On iOS the mechanism is called Universal Links, on Android it is called Android App Links. Both use ordinary https URLs, and both require the app and the website to vouch for each other through a file hosted on the domain.
Build the app first, then wire the links
Get something real running on a device. The two files take an afternoon once you know their exact names and exactly where they go.
Start building