Skip to main content

What is Deep Linking? A Complete Guide for Developers

You've probably tapped a link in an email or a social post and landed on the exact screen inside an app — the product page, the shared playlist, the boarding pass — instead of the app's home screen or a mobile website. That's deep linking. It sounds trivial, but it's one of the fiddlier parts of mobile development, and getting it right is often the difference between a campaign that converts and one that leaks users at every step.

This guide covers what deep linking is, how it works on iOS and Android, the three link types you'll encounter, and how to implement and test them properly.

What is deep linking?

Deep linking is the practice of using a URL to navigate directly to a specific screen or piece of content inside a mobile app. A normal app launch opens the home screen and leaves the user to find their own way. A deep-linked launch hands the app a URL, and the app routes straight to the destination: https://links.example.com/product/12345 opens the product page for item 12345.

On the web this problem was solved decades ago — every URL is a deep link by definition. Mobile operating systems, however, install apps as silos, so the OS needs to be told which app can handle which URLs, and the app needs to know how to turn a URL into navigation. Deep linking is the collection of mechanisms that make that work.

How deep linking works

Every deep link journey follows the same four steps:

  1. The user taps a link — in an email, a chat message, a QR code, or a browser.
  2. The operating system examines the URL and decides who handles it: a verified app, any app that registered the scheme, or the browser.
  3. The chosen app launches and receives the URL.
  4. The app parses the URL and routes to the corresponding screen.

The parsing step is your responsibility as the developer. Given https://links.example.com/product/12345, your router pulls out the path segments, recognizes product/12345 as a product route, and pushes the product screen with id 12345. Everything else — the tap, the OS handoff, the launch — is what the three link types below are about.

The three types of deep links

Custom URL schemes

The original mechanism: your app registers a scheme like myapp://, and any URL beginning with it opens your app (myapp://product/12345). Custom schemes are trivial to set up, but they have two serious flaws. They only work if the app is already installed — otherwise the user sees an ugly error or nothing at all. And there's no ownership check: any app can claim any scheme, so collisions and hijacking are possible. Treat custom schemes as a fallback, not a foundation.

Universal Links (iOS)

Apple's answer is to use ordinary HTTPS URLs on a domain you control. You publish a small JSON file — the Apple App Site Association (AASA) file — at /.well-known/apple-app-site-association on that domain, declaring which app may open which paths. iOS verifies the file cryptographically against your team's signing identity. If the app is installed, tapping https://links.example.com/product/12345 opens the app; if not, the same URL loads your website. The link never dead-ends. We cover the file format in depth in our AASA file guide.

App Links (Android)

Android's equivalent is App Links, verified through a assetlinks.json file at /.well-known/assetlinks.json on your domain. Once Android verifies the association, your HTTPS links open your app directly, skipping the "open with" chooser that unverified links trigger. The verification is tied to your app's signing certificate, which makes it tamper-proof. See our assetlinks.json guide for the full format.

Rule of thumb: build on Universal Links and App Links, keep a custom scheme only as a legacy fallback, and make sure every link you publish has a sensible web destination too.

What deep linking is used for

Anywhere a person meets your app from outside it:

  • Email and push campaigns — "Your order shipped" goes to the order screen, not the homepage.
  • Social and influencer sharing — shared content opens natively in the app instead of a cramped browser view.
  • Referral programs — invite links carry the inviter's identity so both sides get credited.
  • QR codes — on packaging, receipts, and print ads, a code scans straight into the right screen.
  • Paid acquisition — ad clicks land on the advertised content, with the click tied to the install.

The common thread: fewer taps, less drop-off, and — when links are measurable — visibility into which campaigns actually drive opens, installs, and revenue.

A minimal implementation

To register a link handler on each platform, you need a small amount of platform configuration even if you write your UI in a cross-platform framework.

iOS

For a custom scheme, declare it in Info.plist:

<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>com.example.myapp</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

For Universal Links, add the associated-domains entitlement with an applinks:links.example.com entry and publish the AASA file on that domain.

Android

Declare an intent filter in AndroidManifest.xml, with autoVerify so Android validates your assetlinks.json:

<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="links.example.com" />
</intent-filter>

Flutter

The app_links package surfaces both platforms' links as a stream of URIs:

final appLinks = AppLinks();

// Cold start: the link that launched the app
final Uri? first = await appLinks.getInitialLink();
if (first != null) _route(first);

// App already running
appLinks.uriLinkStream.listen(_route);

void _route(Uri uri) {
  // e.g. https://links.example.com/product/12345
  final segments = uri.pathSegments;
  if (segments.length == 2 && segments[0] == 'product') {
    navigator.push(ProductRoute(id: segments[1]));
  }
}

The routing function is where most of the real logic lives — and where most of the bugs live too, so keep it small and well tested.

Best practices

  • Design a link taxonomy you control. Clear, hierarchical, stable paths (/product/:id, /order/:id) age better than ad-hoc URLs, and they make analytics legible.
  • Validate untrusted input. A deep link is user input. Check that ids exist, that users are allowed to see the destination, and handle malformed URLs gracefully.
  • Always have a web fallback. A link should never be a dead end for someone without the app.
  • Test on real devices, both platforms. Emulators lie about link verification, and behavior differs between cold start and foreground resume. Re-test after app updates.
  • Make links measurable. Tie clicks to opens and installs so marketing spend can be judged on outcomes. If the link also survives an install — taking new users straight to the content they clicked — you've arrived at deferred deep linking.

Deep linking with DecTog

DecTog is self-hostable deep linking infrastructure: short links on your own domain, automatic serving of the iOS and Android verification files from your per-app configuration, QR codes for every link, and click analytics with install attribution. Instead of wiring domains, entitlements, and JSON files by hand, you register your app once and DecTog handles the plumbing.

Create a free account to try it, or continue with the guides: What is Deferred Deep Linking?, AASA file format for iOS, and assetlinks.json for Android.