01 · Configure the filter
URL pattern
Activity
Categories -c
Options
02 · Generated XML


                
Verify a URL matches this filter
Quick recipes · tap to load

Path attributes, in order of how often you'll need them

Android gives you four ways to specify which paths an intent filter should match: path, pathPrefix, pathPattern, and pathSuffix. They're mutually exclusive on a given <data> element, and picking the wrong one is the single most common cause of "my deep link works on Stack Overflow's example but not mine." In a typical app you'll mostly use pathPrefix, occasionally reach for pathPattern, and rarely use the other two.

android:pathPrefix

Matches any path starting with the given string. pathPrefix="/products" matches /products, /products/123, /products/abc/details, and /products?id=42. This is what you want for the overwhelming majority of deep links. Anytime your activity handles a "section" of your site (all product pages, all user profiles, all article URLs), pathPrefix is the answer. If you're not sure which to use and your URL pattern starts with a fixed string, this is it.

android:path

Exact-match against one specific path, character for character. Use when an activity only handles a single URL. Typical examples are /legal/privacy or /.well-known/apple-app-site-association. If you find yourself listing five different path attributes in an intent filter, that's a smell. pathPrefix is almost certainly cleaner.

android:pathPattern

Matches paths against a glob-like pattern. Useful when the variable part of the URL is in the middle, not at the end. For example, /users/<id>/posts matches /users/42/posts and /users/abc/posts but not /users/42/profile. The pattern syntax is where most people get tripped up. pathPattern is not a full regex. Android uses a limited glob called PATTERN_SIMPLE_GLOB, and the rules are unintuitive enough that they're worth memorising.

  • . matches any single character, including a literal dot. There's no way to distinguish them without escaping.
  • * matches zero or more occurrences of the preceding character. So a* matches "", "a", "aa", "aaa". * by itself does not mean "any string." That's a regex thing, not a glob thing.
  • .* together means "any sequence of characters." This is the closest equivalent to the regex wildcard most people are reaching for.
  • \\* matches a literal *. \\. matches a literal .. And yes, you need the double backslash because XML eats one of them on the way through the parser.

The practical implication: a pathPattern like /api/v.* matches more than you think, because the . after v matches any character, not just a literal dot. If you mean "v" followed by digits, you'd need a more specific pattern, but the glob doesn't support character classes, so usually pathPrefix="/api/v" is the cleaner answer anyway.

android:pathSuffix (Android 12+)

Matches paths ending with the given string. Added in API level 31 (Android 12), which makes it useless if your minSdkVersion is below 31. The attribute will silently fail to match on older devices. Useful for things like pathSuffix=".pdf" to handle any PDF URL, but if you need older-device support, back-fill with pathPattern=".*\\.pdf".

android:autoVerify and the App Links flow

Setting android:autoVerify="true" on an intent filter for HTTPS URLs declares that your app wants to automatically handle those links instead of competing with the browser via the standard "Open with" picker. To make this work, you also need:

  • An assetlinks.json file at https://yourdomain.com/.well-known/assetlinks.json, served as application/json, containing your app's package name and signing-key SHA-256 fingerprints.
  • Your <data> element to have scheme="https" (or both http and https). Auto-verification doesn't apply to other schemes.
  • Patience. Android verifies on app install in the background and on every update, but it can take seconds to minutes, and it can fail silently if anything is misconfigured.

If verification fails, the user sees the standard disambiguation chooser ("Open with…") instead of your app launching directly. Many users will then click the browser, not your app. Diagnose with:

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

The output shows the verification state per host. Anything other than verified means your assetlinks.json isn't right.

The multiple-<data>-elements gotcha

If your intent filter has more than one <data> element, Android treats their attributes as a cross-product, not as separate filters. This means:

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

...does not mean "match https://example.com OR myapp://other.com." It means "match any combination of the listed schemes and the listed hosts." So it also matches https://other.com and myapp://example.com, which you probably didn't intend. If you want different scheme-host pairs to match, use separate <intent-filter> elements for each.

The BROWSABLE + DEFAULT requirement

For an intent filter to handle deep links arriving from external sources (browsers, messengers, other apps), it needs both:

  • android.intent.category.BROWSABLE: declares the activity can be invoked from a browser or other external context.
  • android.intent.category.DEFAULT: required for any implicit intent. Without this, your activity won't match implicit deep link intents at all, even if everything else is right.

People skip DEFAULT sometimes because their activity also has the LAUNCHER category and works fine "for testing." Then in production, deep links from Gmail or Instagram silently fail to open the app and they spend hours debugging the wrong things.

Android 12+ android:exported

Since Android 12 (API 31), any activity with at least one intent filter must explicitly declare android:exported="true", or the build fails. The tool above includes android:exported="true" when generating an activity wrapper for HTTPS or BROWSABLE intent filters. The value is required for these intent filters to work, and most modern apps handling deep links will want it set.

Subdomain wildcards aren't supported

A common request: "I want my intent filter to match *.example.com, for every subdomain." Android does not support host wildcards in this form. The android:host attribute is matched literally. Workarounds:

  • List each subdomain explicitly with separate <intent-filter> elements (one per host).
  • Use a leading *. only as the very first character. Android does support exactly *.example.com as a leading wildcard host, but support is inconsistent across versions and OEMs. Test before relying on it.
  • For App Links, list every host you intend to verify in your assetlinks.json and in your manifest. Painful but reliable.

What goes wrong, repeatedly

The single most common failure is forgetting BROWSABLE. The activity launches fine when you invoke it programmatically with adb am start, or when you wire it to a button in another app. Then a real user taps a link in Chrome or Gmail and nothing happens. This shows up especially when people copy intent-filter snippets from Stack Overflow that were written for a different use case. A share-target activity, for example, doesn't need BROWSABLE. A deep-link target does.

Reaching for pathPattern when pathPrefix would do is the next-most-common mistake, and it tends to bite worse because the failure is weirder. pathPattern's . wildcard matches more than you expect, and a pattern like /v.* doesn't mean "/v followed by a version number" the way you assumed. If you don't have a variable in the middle of your URL, prefer pathPrefix. It's exact and predictable.

Case sensitivity catches people too. Path attributes are case-sensitive. /Products and /products are different patterns. Hosts are case-insensitive (matched lowercased), but the path matching is strict. If your backend serves both /products and /Products (some CMSes do, irritatingly), you may need both as separate filters.

Setting autoVerify on a non-HTTPS scheme is silently ignored, and the silence is the problem. There's no build warning, no log message, nothing. The attribute only does anything when paired with scheme="http" or scheme="https". If you've set it on a custom scheme and wondered why auto-verification never happens, that's why.

Mixing a custom scheme and HTTPS in one intent filter is technically valid but rarely what you want. The filter matches both schemes, but autoVerify doesn't apply to either, because the attribute requires an HTTPS-only filter. Better to have two separate filters: one for your custom scheme without autoVerify, one for HTTPS with it. The duplication is the price of getting App Links verification to actually fire.

By Belchior · Last updated · May 2026

Copied