{"id":574,"date":"2026-03-22T17:00:00","date_gmt":"2026-03-22T22:00:00","guid":{"rendered":"https:\/\/tolinku.com\/blog\/?p=574"},"modified":"2026-03-07T03:48:18","modified_gmt":"2026-03-07T08:48:18","slug":"android-manifest-deep-link-configuration","status":"publish","type":"post","link":"https:\/\/tolinku.com\/blog\/android-manifest-deep-link-configuration\/","title":{"rendered":"Android Manifest Configuration for Deep Links"},"content":{"rendered":"\n<p>The <code>AndroidManifest.xml<\/code> 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 <code>autoVerify<\/code> flag that enables App Links verification all live here. Getting this configuration right is a prerequisite for Android App Links to work.<\/p>\n\n\n\n<p>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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How Android Routes Deep Links<\/h2>\n\n\n\n<p>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 <code>AndroidManifest.xml<\/code> that tell the system which URLs, schemes, hosts, and paths an activity can handle.<\/p>\n\n\n\n<p>For custom URI schemes (like <code>myapp:\/\/<\/code>), any app can declare any scheme. For HTTPS App Links, Android adds a verification step: the system fetches <code>https:\/\/yourdomain.com\/.well-known\/assetlinks.json<\/code> and confirms that your app is authorized to handle URLs at that domain. This prevents other apps from hijacking your URLs.<\/p>\n\n\n\n<p>The Android documentation on <a href=\"https:\/\/developer.android.com\/training\/app-links\/deep-linking\" rel=\"nofollow noopener\" target=\"_blank\">creating deep links to app content<\/a> covers the routing mechanism in full. The <a href=\"https:\/\/developer.android.com\/training\/app-links\/verify-android-applinks\" rel=\"nofollow noopener\" target=\"_blank\">App Links verification<\/a> documentation covers the verification flow.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The Intent Filter Structure<\/h2>\n\n\n\n<p>A deep link intent filter goes inside an <code>&lt;activity&gt;<\/code> element. Here is the minimal configuration for handling a single HTTPS URL:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;activity\n    android:name=&quot;.MainActivity&quot;\n    android:exported=&quot;true&quot;&gt;\n\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourdomain.com&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<p>Every App Links intent filter requires exactly these three <code>&lt;action&gt;<\/code> and <code>&lt;category&gt;<\/code> elements. They are not optional:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>android.intent.action.VIEW<\/code> tells Android this filter handles view intents (tapped links, programmatic opens)<\/li>\n<li><code>android.intent.category.DEFAULT<\/code> allows the activity to be started by implicit intents<\/li>\n<li><code>android.intent.category.BROWSABLE<\/code> allows the intent to be triggered from a browser or other web-navigating contexts<\/li>\n<\/ul>\n\n\n\n<p>Without <code>BROWSABLE<\/code>, Android will not route tapped links from browsers or other apps to your activity. Without <code>DEFAULT<\/code>, implicit intent routing will not work. Both are required.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>data<\/code> Element in Detail<\/h2>\n\n\n\n<p>The <code>&lt;data&gt;<\/code> element defines which URLs the filter matches. It accepts several attributes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead>\n<tr>\n<th>Attribute<\/th>\n<th>Purpose<\/th>\n<th>Example<\/th>\n<\/tr>\n<\/thead>\n<tbody><tr>\n<td><code>android:scheme<\/code><\/td>\n<td>URL scheme<\/td>\n<td><code>https<\/code> or <code>http<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:host<\/code><\/td>\n<td>Domain name<\/td>\n<td><code>yourdomain.com<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:pathPrefix<\/code><\/td>\n<td>Matches paths starting with a string<\/td>\n<td><code>\/products<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:path<\/code><\/td>\n<td>Matches an exact path<\/td>\n<td><code>\/about<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:pathPattern<\/code><\/td>\n<td>Matches paths by regex-like pattern<\/td>\n<td><code>\/products\/.*<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:pathSuffix<\/code><\/td>\n<td>Matches paths ending with a string<\/td>\n<td><code>.pdf<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>android:port<\/code><\/td>\n<td>Port number (rarely needed)<\/td>\n<td><code>8080<\/code><\/td>\n<\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p>For most production apps, you will use <code>android:pathPrefix<\/code> most often. It matches any URL whose path starts with the given string, which covers most route hierarchies.<\/p>\n\n\n\n<p>A filter for all product pages might look like:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;data\n    android:scheme=&quot;https&quot;\n    android:host=&quot;yourdomain.com&quot;\n    android:pathPrefix=&quot;\/products&quot; \/&gt;\n<\/code><\/pre>\n\n\n\n<p>This matches <code>https:\/\/yourdomain.com\/products<\/code>, <code>https:\/\/yourdomain.com\/products\/123<\/code>, <code>https:\/\/yourdomain.com\/products\/category\/shoes<\/code>, and any other path starting with <code>\/products<\/code>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Path Patterns<\/h3>\n\n\n\n<p><code>android:pathPattern<\/code> uses a limited pattern syntax. The <code>.<\/code> character matches any single character, and <code>.*<\/code> matches any sequence of characters. Other regex features are not supported.<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;!-- Match \/order\/12345 but not \/order\/history --&gt;\n&lt;data\n    android:scheme=&quot;https&quot;\n    android:host=&quot;yourdomain.com&quot;\n    android:pathPattern=&quot;\/order\/[0-9]*&quot; \/&gt;\n<\/code><\/pre>\n\n\n\n<p>Be careful with <code>pathPattern<\/code>: the pattern must match the entire path, not just a substring. If you want to match <code>\/products\/anything<\/code>, use <code>\/products\/.*<\/code>, not <code>\/products<\/code>.<\/p>\n\n\n\n<p>Note that <code>.*<\/code> in Android&#39;s <code>pathPattern<\/code> is not standard regex. The <code>.<\/code> matches any character (including <code>\/<\/code>), so <code>\/products\/.*<\/code> will match <code>\/products\/shoes\/sneakers<\/code> as expected.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>autoVerify<\/code> Attribute<\/h2>\n\n\n\n<p>The <code>android:autoVerify=&quot;true&quot;<\/code> attribute on the <code>&lt;intent-filter&gt;<\/code> element is what enables App Links (as opposed to plain deep links). When this attribute is present, Android will attempt to verify your app&#39;s ownership of the domain by fetching the <code>assetlinks.json<\/code> file.<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    ...\n&lt;\/intent-filter&gt;\n<\/code><\/pre>\n\n\n\n<p>When verification succeeds:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Your app becomes the default handler for the declared URLs<\/li>\n<li>Tapping a matching URL opens your app directly, without a disambiguation dialog<\/li>\n<li>The user is not asked to choose between your app and the browser<\/li>\n<\/ul>\n\n\n\n<p>When verification fails or <code>autoVerify<\/code> is absent:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Matching URLs behave as plain deep links<\/li>\n<li>Android may show a disambiguation dialog asking the user to choose between your app and the browser<\/li>\n<li>On Android 12 and later, unverified links open in the browser by default<\/li>\n<\/ul>\n\n\n\n<p>App Links verification is checked at install time. If <code>assetlinks.json<\/code> 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.<\/p>\n\n\n\n<p>Details about <code>assetlinks.json<\/code> itself, including how to generate and validate it, are covered in the <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">assetlinks.json generator guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The <code>exported<\/code> Attribute<\/h2>\n\n\n\n<p>Android 12 (API level 31) made <code>android:exported<\/code> required for any activity that contains an intent filter. If your target SDK is 31 or higher and you omit <code>exported<\/code>, your build will fail.<\/p>\n\n\n\n<p>For activities that handle deep links (which by definition need to receive intents from external sources), <code>exported<\/code> must be <code>true<\/code>:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;activity\n    android:name=&quot;.MainActivity&quot;\n    android:exported=&quot;true&quot;&gt;\n<\/code><\/pre>\n\n\n\n<p>Activities that do not need to receive external intents should use <code>android:exported=&quot;false&quot;<\/code>. Only activities with deep link intent filters need <code>true<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Multiple Intent Filters for Different Paths<\/h2>\n\n\n\n<p>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.<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;activity\n    android:name=&quot;.MainActivity&quot;\n    android:exported=&quot;true&quot;&gt;\n\n    &lt;!-- Handle all product pages --&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourdomain.com&quot;\n            android:pathPrefix=&quot;\/products&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n\n    &lt;!-- Handle all order pages --&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourdomain.com&quot;\n            android:pathPrefix=&quot;\/orders&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<p>Each intent filter with <code>autoVerify=&quot;true&quot;<\/code> is independent. Verification is performed per intent filter, though in practice the system verifies the domain as a whole.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Routing to Multiple Activities<\/h2>\n\n\n\n<p>For larger apps, you may want different URL patterns to open different activities. Each activity gets its own set of intent filters:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;!-- Product detail activity --&gt;\n&lt;activity\n    android:name=&quot;.ProductDetailActivity&quot;\n    android:exported=&quot;true&quot;&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourdomain.com&quot;\n            android:pathPattern=&quot;\/products\/.*&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n&lt;\/activity&gt;\n\n&lt;!-- User profile activity --&gt;\n&lt;activity\n    android:name=&quot;.ProfileActivity&quot;\n    android:exported=&quot;true&quot;&gt;\n    &lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n        &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n        android.intent.category.BROWSABLE&quot; \/&gt;\n        &lt;data\n            android:scheme=&quot;https&quot;\n            android:host=&quot;yourdomain.com&quot;\n            android:pathPrefix=&quot;\/u&quot; \/&gt;\n    &lt;\/intent-filter&gt;\n&lt;\/activity&gt;\n<\/code><\/pre>\n\n\n\n<p>When two intent filters could both match a URL, Android selects the most specific match. More specific <code>path<\/code> wins over <code>pathPrefix<\/code>, which wins over <code>pathPattern<\/code>. If specificity is equal, behavior is undefined, so keep your patterns non-overlapping.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Handling HTTP and HTTPS<\/h2>\n\n\n\n<p>App Links require HTTPS. However, you might also want to handle <code>http:\/\/<\/code> URLs (for older links or redirects). You can add both schemes in the same intent filter by including multiple <code>&lt;data&gt;<\/code> elements:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;intent-filter android:autoVerify=&quot;true&quot;&gt;\n    &lt;action android:name=&quot;android.intent.action.VIEW&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; \/&gt;\n    &lt;category android:name=&quot;android.intent.category.BROWSABLE&quot; \/&gt;\n    &lt;data android:scheme=&quot;https&quot; android:host=&quot;yourdomain.com&quot; \/&gt;\n    &lt;data android:scheme=&quot;http&quot; android:host=&quot;yourdomain.com&quot; \/&gt;\n&lt;\/intent-filter&gt;\n<\/code><\/pre>\n\n\n\n<p>Important: <code>autoVerify<\/code> 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Multiple Hosts and Subdomains<\/h2>\n\n\n\n<p>If your app should handle URLs from multiple domains (for example, both <code>yourdomain.com<\/code> and <code>www.yourdomain.com<\/code>), add separate <code>&lt;data&gt;<\/code> elements for each host, or use separate intent filters:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;data android:scheme=&quot;https&quot; android:host=&quot;yourdomain.com&quot; \/&gt;\n&lt;data android:scheme=&quot;https&quot; android:host=&quot;www.yourdomain.com&quot; \/&gt;\n<\/code><\/pre>\n\n\n\n<p>Each host you declare requires its own <code>assetlinks.json<\/code> verification. If you declare <code>www.yourdomain.com<\/code> in your manifest but do not host <code>assetlinks.json<\/code> at <code>https:\/\/www.yourdomain.com\/.well-known\/assetlinks.json<\/code> (or redirect it correctly), verification for that host will fail.<\/p>\n\n\n\n<p>Android 12 introduced wildcard host matching. You can use <code>*.yourdomain.com<\/code> to match all subdomains:<\/p>\n\n\n\n<pre><code class=\"language-xml\">&lt;data android:scheme=&quot;https&quot; android:host=&quot;*.yourdomain.com&quot; \/&gt;\n<\/code><\/pre>\n\n\n\n<p>Wildcard host matching requires that a valid <code>assetlinks.json<\/code> exists at each subdomain that should be verified, or that verification is handled at the root domain with proper include statements. The <a href=\"https:\/\/developer.android.com\/training\/app-links\/verify-android-applinks\" rel=\"nofollow noopener\" target=\"_blank\">Android App Links verification documentation<\/a> covers the wildcard verification process in detail.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Reading the Intent in Your Activity<\/h2>\n\n\n\n<p>Once Android routes a URL to your activity, you need to read it and navigate to the right content. In Kotlin:<\/p>\n\n\n\n<pre><code class=\"language-kotlin\">override fun onCreate(savedInstanceState: Bundle?) {\n    super.onCreate(savedInstanceState)\n\n    val intent = intent\n    val action = intent.action\n    val data: Uri? = intent.data\n\n    if (Intent.ACTION_VIEW == action &amp;&amp; data != null) {\n        val path = data.path\n        val params = data.queryParameterNames\n\n        \/\/ Route based on path\n        when {\n            path?.startsWith(&quot;\/products\/&quot;) == true -&gt; {\n                val productId = path.removePrefix(&quot;\/products\/&quot;)\n                navigateToProduct(productId)\n            }\n            path?.startsWith(&quot;\/orders\/&quot;) == true -&gt; {\n                val orderId = path.removePrefix(&quot;\/orders\/&quot;)\n                navigateToOrder(orderId)\n            }\n            else -&gt; navigateToHome()\n        }\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>If your app uses Jetpack Navigation, you can use the <a href=\"https:\/\/developer.android.com\/guide\/navigation\/design\/deep-link\" rel=\"nofollow noopener\" target=\"_blank\">deep links integration<\/a> to handle routing declaratively through your navigation graph rather than with manual path parsing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Testing Your Manifest Configuration<\/h2>\n\n\n\n<p>Android provides the <code>adb<\/code> command-line tool for testing intent filter matching without tapping real links:<\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell am start \\\n  -W -a android.intent.action.VIEW \\\n  -d &quot;https:\/\/yourdomain.com\/products\/123&quot; \\\n  com.yourapp.package\n<\/code><\/pre>\n\n\n\n<p>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.<\/p>\n\n\n\n<p>To test App Links verification specifically, use:<\/p>\n\n\n\n<pre><code class=\"language-bash\">adb shell pm get-app-links com.yourapp.package\n<\/code><\/pre>\n\n\n\n<p>This reports the verification status for each domain your app has declared. A status of <code>verified<\/code> means App Links will work. Any other status means verification has not succeeded and links will fall back to browser behavior.<\/p>\n\n\n\n<p>For the Tolinku platform configuration that connects your manifest setup to your link routing and analytics, see the <a href=\"https:\/\/tolinku.com\/docs\/user-guide\/configuring-android\/\">Android configuration guide<\/a> and the <a href=\"https:\/\/tolinku.com\/docs\/developer\/app-links\/\">App Links developer documentation<\/a>.<\/p>\n\n\n\n<p>For a complete overview of how Android App Links work beyond just the manifest, see the <a href=\"https:\/\/tolinku.com\/blog\/android-app-links-complete-guide\/\">Android App Links complete guide<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Your AndroidManifest.xml is where deep link handling starts. This tutorial covers intent filters, data elements, autoVerify, and every attribute you need to configure Android App Links correctly.<\/p>\n","protected":false},"author":2,"featured_media":573,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"Android Manifest Deep Link Configuration (2026)","rank_math_description":"Step-by-step tutorial on configuring AndroidManifest.xml for Android App Links. Covers intent-filter, data elements, autoVerify, exported activities, and multiple paths.","rank_math_focus_keyword":"android manifest deep links","rank_math_canonical_url":"","rank_math_facebook_title":"","rank_math_facebook_description":"","rank_math_facebook_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-android-manifest-deep-link-configuration.png","rank_math_facebook_image_id":"","rank_math_twitter_title":"","rank_math_twitter_description":"","rank_math_twitter_image":"https:\/\/tolinku.com\/blog\/wp-content\/uploads\/2026\/03\/og-android-manifest-deep-link-configuration.png","footnotes":""},"categories":[10],"tags":[25,99,23,101,20,100,72],"class_list":["post-574","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-android","tag-android","tag-androidmanifest","tag-app-links","tag-configuration","tag-deep-linking","tag-intent-filter","tag-tutorial"],"_links":{"self":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/574","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/comments?post=574"}],"version-history":[{"count":1,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/574\/revisions"}],"predecessor-version":[{"id":575,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/posts\/574\/revisions\/575"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media\/573"}],"wp:attachment":[{"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/media?parent=574"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/categories?post=574"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tolinku.com\/blog\/wp-json\/wp\/v2\/tags?post=574"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}