All articles
Guide ·

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.

  • android deep links
  • android development
  • app links
  • firebase dynamic links
  • mobile development

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 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.

A hand-drawn illustration explaining Android deep link architecture, highlighting URI schemes, intent filters, and navigation benefits.

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:

  • VIEW action tells Android this activity can open externally provided content.
  • BROWSABLE category allows the activity to be started from a browser or another app.
  • DEFAULT category lets the activity participate in standard intent resolution.
  • data element 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:

  1. Receive the intent in the target activity.
  2. Read intent.data and extract path segments or query parameters.
  3. Normalize the route before navigation.
  4. 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.

A diagram illustrating the four-step Android App Links process for opening applications directly from web links.

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.

A conversion funnel infographic illustrating the five steps of the deferred deep linking process for mobile apps.

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.

Screenshot from https://302.sh

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.

Short links that keep working.
Fairly priced.