Skip to content
Tolinku
Tolinku
Sign In Start Free
Android Development · · 9 min read

Android Intent Filters for Deep Links: Configuration Guide

By Tolinku Staff
|
Tolinku app links dashboard screenshot for android blog posts

Every Android deep link starts with an intent filter in AndroidManifest.xml. This XML configuration tells Android which URLs your activity can handle. Get it right, and your app intercepts the correct links. Get it wrong, and you end up either missing links you want to handle or claiming links you shouldn't.

This guide covers the full intent filter configuration for deep links and Android App Links: what each element and attribute does, how path matching patterns work, how to set up multiple filters for different URL structures, and how Android decides which app handles a given URL when multiple candidates exist. For a broader overview of the App Links system, see the Android App Links Complete Guide.

The Basic Structure

An intent filter for deep links lives inside an <activity> element in your AndroidManifest.xml. Here is the minimal configuration for an App Link (a verified https link):

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

</activity>

Each element in this filter has a specific role:

  • <action android:name="android.intent.action.VIEW" />: Required. Tells Android this activity can display content.
  • <category android:name="android.intent.category.DEFAULT" />: Required. Makes the activity reachable via implicit intents.
  • <category android:name="android.intent.category.BROWSABLE" />: Required for links opened from a browser or other apps. Without this, a tap on a URL in Chrome will never reach your app.
  • <data>: Defines which URLs this filter matches.
  • android:autoVerify="true": On the <intent-filter> element itself, not on <data>. This triggers the App Links verification process, which is what turns a regular deep link into a verified App Link that skips the disambiguation dialog.

The android:exported="true" attribute on the activity is required on Android 12 (API 31) and later for any activity that declares an intent filter. Android will refuse to install apps that omit this on API 31+ targets.

The <data> Element Attributes

The <data> element is where you specify what URLs match. It supports several attributes that combine to describe a URL pattern.

android:scheme

The URL scheme. For App Links, use "https". For regular deep links, you might use a custom scheme like "myapp". You can also use "http", though App Links verification only applies to https schemes.

<data android:scheme="https" />

android:host

The hostname the URL must match. This can be an exact hostname or use a wildcard prefix.

<!-- Exact match -->
<data android:host="example.com" />

<!-- Match any subdomain of example.com -->
<data android:host="*.example.com" />

The wildcard *.example.com matches www.example.com, app.example.com, and any other single-level subdomain, but it does not match example.com itself. If you want both the root domain and subdomains, you need separate <data> elements or separate intent filters.

Important for App Links: When you use a wildcard host with autoVerify="true", Android attempts verification for each specific subdomain it encounters in links, not for the wildcard pattern itself. You need the assetlinks.json file on each subdomain you want to handle. This can get complicated; most teams find it simpler to use explicit hostnames.

android:port

Optional. If your server runs on a non-standard port during development, you can specify it:

<data android:scheme="https" android:host="localhost" android:port="8443" />

For production App Links, you will not normally use this attribute.

android:path, android:pathPrefix, and android:pathPattern

These attributes restrict which paths within the host the filter matches.

android:path matches one exact path:

<data android:path="/invite" />

This matches https://example.com/invite but not https://example.com/invite/abc123.

android:pathPrefix matches any path that starts with the given string:

<data android:pathPrefix="/products" />

This matches /products, /products/123, /products/abc/details, and so on.

android:pathPattern matches paths using a simplified pattern syntax. The . character matches any single character, and .* matches any sequence of characters (including an empty sequence):

<!-- Match /user/ followed by any characters -->
<data android:pathPattern="/user/.*" />

<!-- Match /item/ followed by exactly one character -->
<data android:pathPattern="/item/." />

Note that this pattern syntax is not the same as standard regex. The only special characters are . (any single character) and * (zero or more of the preceding character). To match a literal dot, escape it with a double backslash: \\..

android:pathAdvancedPattern (added in API 31) supports standard regex syntax for more precise matching:

<data android:pathAdvancedPattern="/products/[0-9]+" />

If your minSdkVersion is below 31, you cannot rely on this attribute alone. You would need a fallback with pathPattern or handle path validation in code.

android:mimeType

You can also match by MIME type. This is more relevant for content sharing than for deep linking, but it is available if your use case requires it.

How Android Evaluates Multiple Attributes

When you put multiple attributes on a single <data> element, they all apply together (AND logic). The URL must match every attribute that is specified.

When you have multiple <data> elements inside a single <intent-filter>, Android pools all of the values across all <data> elements within that filter (OR-like pooling). This is a common source of confusion.

Consider this filter:

<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="example.com" />
    <data android:scheme="https" android:host="www.example.com" />
</intent-filter>

This filter matches both https://example.com/... and https://www.example.com/.... The two <data> elements each contribute their host value to the pool.

But the pooling behavior can also cause unintended matches. If you write:

<intent-filter>
    <data android:scheme="https" android:host="example.com" />
    <data android:scheme="myapp" />
</intent-filter>

Android pools scheme values (https, myapp) and host values (example.com). The result is that this filter matches both https://example.com/... AND myapp://example.com/.... It also potentially matches https:// and myapp:// without any host restriction (because the second <data> element contributes no host). This is almost certainly not what you intended.

Best practice: Use separate <intent-filter> elements for different schemes. Do not mix schemes in a single filter.

Separate Filters for Different URL Patterns

You can add multiple <intent-filter> elements to the same activity. This is the correct way to handle different URL structures cleanly:

<activity
    android:name=".MainActivity"
    android:exported="true">

    <!-- App Links for the main domain -->
    <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="example.com"
            android:pathPrefix="/app" />
    </intent-filter>

    <!-- Custom scheme for legacy compatibility -->
    <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" />
    </intent-filter>

</activity>

When autoVerify="true" is on a filter, it only triggers verification for the domains declared in that filter. The custom scheme filter has no effect on App Links verification.

Routing to Different Activities

You are not limited to a single activity. Different URL patterns can route to different activities by placing intent filters on each:

<!-- Main activity for general links -->
<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="example.com" />
    </intent-filter>
</activity>

<!-- Product activity for product pages -->
<activity android:name=".ProductActivity" 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="example.com"
            android:pathPrefix="/products" />
    </intent-filter>
</activity>

When a URL matches multiple filters across multiple activities, Android applies a specificity rule: more specific filters win. A filter with both a host and a path prefix is more specific than one with only a host. In the example above, https://example.com/products/123 matches both filters, but Android chooses ProductActivity because its path prefix makes it the more specific match.

If two filters have equal specificity across different apps, Android shows the disambiguation dialog (unless one of the apps has verified App Links status).

The autoVerify Attribute and Partial Verification

android:autoVerify="true" belongs on the <intent-filter> element, and it triggers verification for every HTTPS domain listed in that filter's <data> elements.

If verification fails for any one domain in a filter, Android will not grant verified status for the domains in that filter (on Android 11 and earlier). On Android 12 and later, verification is per-domain: a failure on one domain does not affect the verified status of other domains that passed.

See the Tolinku App Links developer documentation for details on how Tolinku handles multi-domain configurations and the assetlinks.json setup that goes alongside your manifest configuration.

Reading the Intent in Your Activity

Once Android routes a URL to your activity, you read the incoming URL from the intent:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val intent = intent
    val action = intent.action
    val data = intent.data

    if (Intent.ACTION_VIEW == action && data != null) {
        val path = data.path          // e.g. "/products/123"
        val productId = data.lastPathSegment  // e.g. "123"
        val queryParam = data.getQueryParameter("ref")

        // Navigate to the appropriate screen
        handleDeepLink(path, productId, queryParam)
    }
}

The Intent.getData() method returns a Uri object. The Uri class provides methods to extract each component of the URL: getScheme(), getHost(), getPath(), getPathSegments(), getQueryParameter(), and others. You do not need to parse the URL string manually. For more on handling deep link data in Kotlin, see Kotlin Deep Link Handling: Patterns and Best Practices.

For links that arrive while the app is already running in the background, also override onNewIntent():

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    // Handle the new deep link intent
    intent.data?.let { handleDeepLink(it) }
}

Testing Your Configuration

After configuring your intent filters, verify the setup with adb before testing manually:

adb shell am start -a android.intent.action.VIEW \
  -d "https://example.com/products/123" \
  com.example.myapp

If the activity launches, the intent filter is configured correctly. If nothing happens, check the filter configuration and make sure the package name is correct.

For App Links specifically, also check verification status:

adb shell pm get-app-links --package com.example.myapp

For a complete testing walkthrough, see the Tolinku Android troubleshooting guide and the testing Android App Links guide.

Summary

Intent filters are the first step in any Android deep link implementation. Use android.intent.action.VIEW, both the DEFAULT and BROWSABLE categories, and the <data> element to specify your URL patterns. For App Links, add android:autoVerify="true" to the intent filter element. Keep different schemes in separate filters to avoid unintended URL matches. Use multiple intent filters or multiple activities to route different URL patterns to different destinations. Once the manifest is configured, read the incoming URL from Intent.getData() in your activity's onCreate() and onNewIntent() methods. For a detailed walkthrough of the full manifest setup, see Android Manifest Deep Link Configuration.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.