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

Android Manifest Configuration for Deep Links

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

The AndroidManifest.xml file is the entry point for all deep link configuration in an Android app. Every URL your app should handle, every path pattern it should match, and the autoVerify flag that enables App Links verification all live here. Getting this configuration right is a prerequisite for Android App Links to work.

This tutorial walks through every relevant element and attribute, from a basic single-activity setup to more complex configurations with multiple activities and path patterns.

When a user taps a URL, Android checks which installed apps have declared an interest in handling that URL. This matching happens through intent filters: XML declarations in AndroidManifest.xml that tell the system which URLs, schemes, hosts, and paths an activity can handle.

For custom URI schemes (like myapp://), any app can declare any scheme. For HTTPS App Links, Android adds a verification step: the system fetches https://yourdomain.com/.well-known/assetlinks.json and confirms that your app is authorized to handle URLs at that domain. This prevents other apps from hijacking your URLs.

The Android documentation on creating deep links to app content covers the routing mechanism in full. The App Links verification documentation covers the verification flow.

The Intent Filter Structure

A deep link intent filter goes inside an <activity> element. Here is the minimal configuration for handling a single HTTPS URL:

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

Every App Links intent filter requires exactly these three <action> and <category> elements. They are not optional:

  • android.intent.action.VIEW tells Android this filter handles view intents (tapped links, programmatic opens)
  • android.intent.category.DEFAULT allows the activity to be started by implicit intents
  • android.intent.category.BROWSABLE allows the intent to be triggered from a browser or other web-navigating contexts

Without BROWSABLE, Android will not route tapped links from browsers or other apps to your activity. Without DEFAULT, implicit intent routing will not work. Both are required.

The data Element in Detail

The <data> element defines which URLs the filter matches. It accepts several attributes:

Attribute Purpose Example
android:scheme URL scheme https or http
android:host Domain name yourdomain.com
android:pathPrefix Matches paths starting with a string /products
android:path Matches an exact path /about
android:pathPattern Matches paths by regex-like pattern /products/.*
android:pathSuffix Matches paths ending with a string .pdf
android:port Port number (rarely needed) 8080

For most production apps, you will use android:pathPrefix most often. It matches any URL whose path starts with the given string, which covers most route hierarchies.

A filter for all product pages might look like:

<data
    android:scheme="https"
    android:host="yourdomain.com"
    android:pathPrefix="/products" />

This matches https://yourdomain.com/products, https://yourdomain.com/products/123, https://yourdomain.com/products/category/shoes, and any other path starting with /products.

Path Patterns

android:pathPattern uses a limited pattern syntax. The . character matches any single character, and .* matches any sequence of characters. Other regex features are not supported.

<!-- Match /order/12345 but not /order/history -->
<data
    android:scheme="https"
    android:host="yourdomain.com"
    android:pathPattern="/order/[0-9]*" />

Be careful with pathPattern: the pattern must match the entire path, not just a substring. If you want to match /products/anything, use /products/.*, not /products.

Note that .* in Android's pathPattern is not standard regex. The . matches any character (including /), so /products/.* will match /products/shoes/sneakers as expected.

The autoVerify Attribute

The android:autoVerify="true" attribute on the <intent-filter> element is what enables App Links (as opposed to plain deep links). When this attribute is present, Android will attempt to verify your app's ownership of the domain by fetching the assetlinks.json file.

<intent-filter android:autoVerify="true">
    ...
</intent-filter>

When verification succeeds:

  • Your app becomes the default handler for the declared URLs
  • Tapping a matching URL opens your app directly, without a disambiguation dialog
  • The user is not asked to choose between your app and the browser

When verification fails or autoVerify is absent:

  • Matching URLs behave as plain deep links
  • Android may show a disambiguation dialog asking the user to choose between your app and the browser
  • On Android 12 and later, unverified links open in the browser by default

App Links verification is checked at install time. If assetlinks.json is unreachable when the app installs, verification fails. The system will retry periodically, but there can be a delay before verification succeeds after fixing a misconfiguration.

Details about assetlinks.json itself, including how to generate and validate it, are covered in the assetlinks.json generator guide.

The exported Attribute

Android 12 (API level 31) made android:exported required for any activity that contains an intent filter. If your target SDK is 31 or higher and you omit exported, your build will fail.

For activities that handle deep links (which by definition need to receive intents from external sources), exported must be true:

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

Activities that do not need to receive external intents should use android:exported="false". Only activities with deep link intent filters need true.

Multiple Intent Filters for Different Paths

You can add multiple intent filters to a single activity. Each filter is evaluated independently. This is the correct way to route different URL paths to the same activity when you want to handle them in the activity code rather than routing to separate activities.

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

    <!-- Handle all product pages -->
    <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"
            android:pathPrefix="/products" />
    </intent-filter>

    <!-- Handle all order pages -->
    <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"
            android:pathPrefix="/orders" />
    </intent-filter>

</activity>

Each intent filter with autoVerify="true" is independent. Verification is performed per intent filter, though in practice the system verifies the domain as a whole.

Routing to Multiple Activities

For larger apps, you may want different URL patterns to open different activities. Each activity gets its own set of intent filters:

<!-- Product detail activity -->
<activity
    android:name=".ProductDetailActivity"
    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"
            android:pathPattern="/products/.*" />
    </intent-filter>
</activity>

<!-- User profile activity -->
<activity
    android:name=".ProfileActivity"
    android:exported="true">
    <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        android.intent.category.BROWSABLE" />
        <data
            android:scheme="https"
            android:host="yourdomain.com"
            android:pathPrefix="/u" />
    </intent-filter>
</activity>

When two intent filters could both match a URL, Android selects the most specific match. More specific path wins over pathPrefix, which wins over pathPattern. If specificity is equal, behavior is undefined, so keep your patterns non-overlapping.

Handling HTTP and HTTPS

App Links require HTTPS. However, you might also want to handle http:// URLs (for older links or redirects). You can add both schemes in the same intent filter by including multiple <data> elements:

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

Important: autoVerify only applies to the HTTPS scheme. HTTP URLs are not verified. If you include both, the HTTP filter behaves as a plain deep link (with a disambiguation dialog or browser fallback), while the HTTPS filter benefits from verification.

Multiple Hosts and Subdomains

If your app should handle URLs from multiple domains (for example, both yourdomain.com and www.yourdomain.com), add separate <data> elements for each host, or use separate intent filters:

<data android:scheme="https" android:host="yourdomain.com" />
<data android:scheme="https" android:host="www.yourdomain.com" />

Each host you declare requires its own assetlinks.json verification. If you declare www.yourdomain.com in your manifest but do not host assetlinks.json at https://www.yourdomain.com/.well-known/assetlinks.json (or redirect it correctly), verification for that host will fail.

Android 12 introduced wildcard host matching. You can use *.yourdomain.com to match all subdomains:

<data android:scheme="https" android:host="*.yourdomain.com" />

Wildcard host matching requires that a valid assetlinks.json exists at each subdomain that should be verified, or that verification is handled at the root domain with proper include statements. The Android App Links verification documentation covers the wildcard verification process in detail.

Reading the Intent in Your Activity

Once Android routes a URL to your activity, you need to read it and navigate to the right content. In Kotlin:

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

    val intent = intent
    val action = intent.action
    val data: Uri? = intent.data

    if (Intent.ACTION_VIEW == action && data != null) {
        val path = data.path
        val params = data.queryParameterNames

        // Route based on path
        when {
            path?.startsWith("/products/") == true -> {
                val productId = path.removePrefix("/products/")
                navigateToProduct(productId)
            }
            path?.startsWith("/orders/") == true -> {
                val orderId = path.removePrefix("/orders/")
                navigateToOrder(orderId)
            }
            else -> navigateToHome()
        }
    }
}

If your app uses Jetpack Navigation, you can use the deep links integration to handle routing declaratively through your navigation graph rather than with manual path parsing.

Testing Your Manifest Configuration

Android provides the adb command-line tool for testing intent filter matching without tapping real links:

adb shell am start \
  -W -a android.intent.action.VIEW \
  -d "https://yourdomain.com/products/123" \
  com.yourapp.package

This sends a VIEW intent directly to your app and reports which activity handled it. Useful for confirming that path patterns match the URLs you expect.

To test App Links verification specifically, use:

adb shell pm get-app-links com.yourapp.package

This reports the verification status for each domain your app has declared. A status of verified means App Links will work. Any other status means verification has not succeeded and links will fall back to browser behavior.

For the Tolinku platform configuration that connects your manifest setup to your link routing and analytics, see the Android configuration guide and the App Links developer documentation.

For a complete overview of how Android App Links work beyond just the manifest, see the Android App Links complete guide.

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.