Android Deep Links: Master Your App in 2026
Master Android deep links in 2026. This guide covers intent filters, App Links, Firebase Dynamic Links, testing, and best practices to boost engagement.
You're probably here because one of two things is happening.
A link in your app campaign opens the wrong screen, or it opens the browser instead of the app. And when you try to “just add deep links,” you discover Android has three overlapping concepts, legacy examples still rank well in search, and half the debugging advice skips the part that breaks on real devices.
That's why Android deep links feel harder than they should. The XML is only the start. The main effort is choosing the right link type, handling the install gap, keeping navigation stable, and giving product or marketing a setup they can operate without a new release every time a destination changes.
Table of Contents
- Why Your App Needs Android Deep Links
- The Foundation with URI Schemes and Intent Filters
- Achieving Seamless UX with Android App Links
- Solving the App Not Installed Problem
- Testing, Best Practices, and Common Pitfalls
- Managing and Analyzing Your Links at Scale
Why Your App Needs Android Deep Links
The simplest way to understand Android deep links is to look at a bad mobile journey.
A user taps a link for a specific product, article, playlist, or checkout flow. Your app opens, but it dumps them on the home screen. The user now has to search again, re-enter context, and rebuild intent you already had in the URL. That's friction you created after they already said yes.
A good deep link does the opposite. It opens the app and lands the user on the exact screen they expected. That sounds like a small polish item, but it changes how the whole product feels. The app stops acting like a disconnected island and starts behaving like part of the web, your email campaigns, QR codes, ads, and lifecycle messaging.
According to an academic analysis of Android apps, about 73% of Android applications don't implement deep links, and only 18% contain exactly one in the sampled ecosystem, which shows how underused this capability still is (academic deep link study on arXiv). That same source is a useful reminder that development teams often treat deep linking as optional plumbing instead of basic app navigation infrastructure.
The business reason is user intent
When someone taps a link, they've already told you what they want. If your app ignores that signal, every downstream metric gets worse for reasons that are hard to spot in code review.
AppsFlyer reports that correctly implemented deep links can drive up to 4× higher conversion rates compared to non-deep-linked campaigns (AppsFlyer on Android deep linking). The number matters, but the mechanism matters more. You remove steps. You preserve intent. You shorten the path between click and action.
Practical rule: Deep links aren't a marketing add-on. They're navigation contracts between every external entry point and a specific in-app destination.
Why developers should care
This isn't only about acquisition campaigns. It affects support links, password reset flows, referral invites, content sharing, notifications, and QR code experiences on packaging or events.
If you build Android apps long enough, you learn that users rarely describe this as “a deep link bug.” They say things like:
- “The app opened to the wrong page.”
- “The offer disappeared after install.”
- “The link worked on one phone and not another.”
- “It opens Chrome instead of the app.”
Those are deep linking problems. They just arrive disguised as UX issues, attribution issues, or flaky navigation bugs.
The Foundation with URI Schemes and Intent Filters
The oldest form of Android deep links uses a custom URI scheme such as myapp://product/123. It still works, and you should understand it, because every modern setup builds on the same intent resolution model.

At the Android level, the flow is simple. A link is turned into an intent. The system checks which activity can handle it. Your manifest tells Android which URL patterns belong to your app.
A minimal manifest example
Here's a basic custom scheme setup:
<activity
android:name=".ProductActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="myapp"
android:host="product" />
</intent-filter>
</activity>
If you launch myapp://product?id=123, Android can route that intent to ProductActivity.
The key parts matter:
VIEWaction tells Android this activity can open externally provided content.BROWSABLEcategory allows the activity to be started from a browser or another app.DEFAULTcategory lets the activity participate in standard intent resolution.dataelement defines what URI pattern the activity accepts.
What this gets right and where it breaks
Custom schemes are fast to add and easy to test. They're useful for internal prototypes, private integrations, and app-to-app flows where you control both ends.
They also have clear limits.
| Approach | Good for | Weak point |
|---|---|---|
| Custom URI schemes | Installed-app routing | No built-in trust for public web links |
| HTTPS App Links | Web-to-app UX | Requires domain verification |
| Deferred deep links | Install flows | More moving parts |
The biggest issue is ambiguity. If another app claims the same scheme, Android may show a chooser dialog. That's a poor user experience and one reason scheme-based linking is considered legacy for public entry points.
A custom scheme is best treated as a private doorway. If you expect users to tap links from the web, email, social, or QR codes, HTTPS-based App Links are the stronger default.
Routing inside the app
The manifest only gets the user into your process. You still need to parse the URI and map it to real navigation.
A clean pattern looks like this:
- Receive the intent in the target activity.
- Read
intent.dataand extract path segments or query parameters. - Normalize the route before navigation.
- Send the user to a screen model, not straight into ad hoc fragment transactions.
For example:
val uri = intent?.data
val productId = uri?.getQueryParameter("id")
if (productId != null) {
navController.navigate("product/$productId")
} else {
navController.navigate("home")
}
That works, but don't let this logic spread across multiple activities. Once your app supports promos, referrals, articles, carts, and auth callbacks, deep linking becomes a routing problem, not a one-off manifest trick.
Achieving Seamless UX with Android App Links
If custom schemes are the legacy baseline, Android App Links are the modern setup for public URLs. They use normal https:// links and let Android verify that your app is allowed to open content from your domain.
That verification is what removes the “Open with...” friction. Without it, users can get bounced into a chooser or back to the browser. With it, the app opens directly to the destination you intended.

Google's Android Developers guidance lays out a practical six-step process: declare the right intent filter, host assetlinks.json, include the delegate_permission/common.handle_all_urls relation, set android:autoVerify="true", wait for verification, and validate with ADB. It also notes that unverified links often cause a 30–40% drop in conversion rates because users dismiss the confirmation dialog (Android Developers deep links crash course on Medium).
The manifest setup
A typical App Links filter looks like this:
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="https"
android:host="yourdomain.com" />
</intent-filter>
</activity>
Two implementation details matter a lot.
First, use HTTPS for your public deep links. Second, keep this filter focused. Don't treat it like a catch-all dumping ground for every route style your app has ever supported.
The assetlinks.json file
This file must live at:
https://yourdomain.com/.well-known/assetlinks.json
Its job is to prove the relationship between your website and your Android package. A minimal example looks like this:
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"YOUR_SHA256_FINGERPRINT"
]
}
}
]
You need the exact package name and the correct signing certificate fingerprint for the app build you ship.
What works well in practice
A stable App Links setup usually has these traits:
- One verified domain strategy: Teams get into trouble when staging, production, vanity campaign domains, and legacy redirect domains all compete for ownership.
- Explicit path handling: Decide which URLs should open in-app and which should stay on the web.
- A routing layer inside the app: App Links solve entry. They don't solve what should happen after launch.
For teams that need controlled traffic distribution before the app ever sees the click, a separate smart link routing workflow is often easier to manage than hard-coding every external path decision inside the Android app.
Verified App Links are best when you want the web URL itself to be the canonical entry point. That's usually what product, marketing, and users all expect.
The why behind App Links
Use URI schemes when you control the environment. Use App Links when you want trust, direct opens, and predictable web-to-app behavior.
That distinction saves a lot of wasted time. Many broken Android deep links happen because developers start with old custom-scheme tutorials, then bolt HTTPS support onto the side without adopting the verification model that makes App Links reliable.
Solving the App Not Installed Problem
A standard deep link can only help if the app already exists on the device. That's the point where many otherwise good implementations fall apart.
A user taps a campaign link, doesn't have the app, gets sent to Google Play, installs, opens the app, and lands on the home screen. The original destination is gone. So is the campaign context. The journey technically “worked,” but the intent that made the click valuable got lost in the middle.

That's where deferred deep linking comes in. It preserves the intended destination through the install flow and restores it on first open.
Why this is harder on Android
Branch notes that deferred deep linking on Android succeeds at about 65–70%, lower than iOS, largely because of platform fragmentation and fallback complexity (Branch guide to Android deep linking). That same source calls out a major real-world problem: many implementations redirect to the store but fail to preserve the original path for post-install recovery.
Managed platforms demonstrate their value. Services such as Firebase Dynamic Links have been popular because they reduce the number of places you can lose context.
A practical mental model:
| Scenario | Plain deep link | App Link | Deferred deep link |
|---|---|---|---|
| App installed | Opens app | Opens app if verified | Opens app |
| App not installed | Fails or does nothing useful | Usually falls back to web | Sends to store and can recover destination later |
| Keeps original intent after install | No | Not by itself | Yes, if configured well |
Build it yourself or use a managed service
If you roll your own fallback, you need to handle several pieces cleanly:
- Capture the original destination before the store redirect happens.
- Persist enough context to recover it after install.
- Handle browser differences and Android version quirks.
- Avoid broken loops where the user bounces between web, store, and app.
A managed approach simplifies that. You get one shareable link that can branch based on install state and restore context on first launch.
For teams that also need short URLs for campaigns or QR-friendly links, a separate guide to creating a bit link alternative is useful because the operational side of link distribution is often a different problem from the in-app routing code.
Later in the flow, seeing the install journey helps more than another paragraph of theory:
What usually fails
In practice, deferred deep linking breaks for ordinary reasons, not exotic ones.
- Missing fallback preservation: The store redirect happens, but the original URL path or parameters are discarded.
- Overeager redirect scripts: The web layer sends users away too quickly and doesn't give the platform enough context to resolve the install flow properly.
- Weak first-open handling: The app installs correctly but ignores the recovered link payload and defaults to home.
- Cross-platform abstractions: React Native or Flutter linking layers can hide Android-specific behavior until something degrades imperceptibly.
If the app isn't installed, the question isn't “can I get them to the store?” It's “can I get them back to the exact content they wanted after install?”
That's the difference between a deep link system and a store redirect.
Testing, Best Practices, and Common Pitfalls
Deep links that work once on your own phone aren't done. They're barely started.
Android deep link bugs often live in the gap between manifest intent matching, OS verification state, browser behavior, and your own navigation stack. The fastest way to debug them is to stop guessing and test the exact entry conditions.
Use ADB before you touch app code
For App Links verification, one of the most useful commands is:
adb shell pm get-app-links your.package.name
That command matters because Android 12 and above can refuse verification, without providing immediate feedback, in situations that look fine in the manifest. A known failure mode is mixing custom URI schemes such as myapp:// with HTTPS links in the same intent filter, which can cause Android to reject auto-verification for the HTTPS domain (Stack Overflow discussion of the Android 12 App Links verification issue).
To launch a test URL manually, use:
adb shell am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://yourdomain.com/product/123"
That lets you test resolution without waiting for a real email, ad, or QR code scan.
Pitfalls that waste the most time
Some failures are subtle enough that developers blame the wrong layer.
Keep custom schemes and HTTPS separate
If you support both myapp:// and https://yourdomain.com, define them in separate intent filters. Don't combine them into one out of convenience.
That separation is especially important on Android 12+, where verification behavior got stricter for web intents.
Don't let launch mode break navigation
If a deep link can reopen an existing task, think carefully about how the target activity behaves. The wrong setup can create duplicate activity instances, stale extras, or a corrupted back stack.
A reliable pattern is to route the incoming URI through a single entry point, then hand off to your app's navigation layer with clear idempotent logic.
Parse once, route once
Scattered URI parsing is one of the most common causes of inconsistent behavior. You tap the same link from Chrome, a notification, and an in-app webview, and each path handles parameters a little differently.
Use a dedicated router object or module. Give it one responsibility: accept a Uri, validate it, normalize it, and return a navigation target.
A practical test checklist
Run this list every time you ship or change Android deep links:
- Installed app test: Open the deep link with the app installed and confirm the exact screen and state.
- Cold start test: Force stop the app, then open the link again. Cold starts expose initialization races.
- Backgrounded app test: Open the link while the app already exists in recents and verify back-stack behavior.
- Uninstalled app test: Check the fallback path and confirm users don't hit a dead end.
- Malformed URL test: Remove required parameters and confirm your app fails safely.
- Verification state test: Use ADB to inspect whether the domain is approved.
Broken deep linking rarely comes from one giant mistake. It usually comes from three small “good enough” decisions that only fail when combined on a real device.
If you treat testing as part of navigation, not part of marketing, your implementation gets much more reliable.
Managing and Analyzing Your Links at Scale
Once Android deep links work, a new problem appears. Managing them directly in app code doesn't scale well.
If every campaign, promo, QR code, influencer link, region-specific landing page, and platform branch requires an app release or hard-coded destination map, your linking system becomes expensive to operate. Product wants flexibility. Marketing wants analytics. Engineering wants fewer deploys for URL changes.
Raw deep links are too rigid
A direct App Link such as https://yourdomain.com/promo/spring-sale is fine when you have one destination and one platform path.
It gets messy when you need rules like these:
- Android users should open the app.
- iPhone users should go somewhere else.
- Desktop visitors should land on a web page.
- Event flyers need QR codes.
- One campaign slug should survive destination changes over time.
That's where a link management layer helps. Instead of publishing raw destinations everywhere, you publish a controlled link and decide routing rules in one place.
What to manage outside the app
The useful features aren't glamorous. They're operational.

A practical setup usually needs:
| Need | Why it matters |
|---|---|
| Branded short links | Easier to share than long campaign URLs |
| Device-based routing | Android, iOS, and desktop often need different destinations |
| QR code generation | Print and packaging don't get clickable anchors |
| Click analytics | You need to know whether a link is being used at all |
| Destination updates | Campaigns change faster than app release cycles |
A separate performance metric also matters when you're evaluating campaign links. If you want a benchmark for link engagement quality, this explanation of what makes a good CTR is a useful framing reference.
What good operations look like
A manageable deep linking program usually follows a few rules:
- Use canonical slugs: Create stable campaign links that can outlive the current destination.
- Keep app routes predictable: The app should accept clear route structures, not ad hoc one-offs invented per campaign.
- Separate routing from rendering: External rules decide where a user should go. The app decides how to render the destination.
- Track the click before the app opens: Once control passes to the OS, debugging gets harder.
This matters most for small teams. You don't need enterprise mobile attribution software to benefit from cleaner routing and simpler analytics. You do need a setup that lets non-developers adjust where links go without turning every change into a ticket.
The moment Android deep links move beyond one or two hard-coded examples, you're not just implementing links anymore. You're operating a distribution system.
If you want a simpler way to run that system, 302.sh gives small teams branded short links, smart routing, QR codes, and privacy-first analytics without putting your redirect path behind metered clicks. It's a practical fit for campaigns that need to send Android users into app links, route other visitors elsewhere, and keep the link manageable after launch.