Fix Universal Pattern Links in AndroidManifest: 3 Tips 2025
Struggling with Universal Pattern Links in your AndroidManifest? Learn 3 expert tips for 2025 to fix broken deep links and ensure a seamless user experience.
Alex Ivanov
Senior Android Engineer specializing in app architecture and deep linking.
We’ve all been there. You’ve spent days, maybe weeks, implementing what should be a straightforward feature: deep linking. You click the test link on your device, holding your breath... only to be greeted by the dreaded "Open with..." disambiguation dialog. Or worse, it just opens in Chrome, completely ignoring your beautiful, native app experience. Your heart sinks. Your universal links—or as Android calls them, App Links—are broken.
This struggle is a rite of passage for many Android developers. While the concept of seamlessly transitioning a user from a web URL to a specific screen in your app is powerful, the implementation details are notoriously finicky. A single misplaced character in your AndroidManifest.xml
or a subtle server misconfiguration can send you down a rabbit hole of debugging. But fear not! As we head into 2025, the core principles remain the same, and mastering them is easier than you think.
Tip 1: Master the `path`, `pathPrefix`, and `pathPattern` Attributes
The heart of your app's link-handling logic lives within an <intent-filter>
in your AndroidManifest.xml
. Getting the host and scheme right is usually easy, but the real confusion often lies in choosing and correctly using the three `path` attributes. Using the wrong one or misunderstanding its syntax is the #1 manifest-related error.
<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="www.yourapp.com"
android:pathPattern="/product/.*" /> <!-- The tricky part -->
</intent-filter>
`path`, `pathPrefix`, and `pathPattern`: What's the Difference?
Let's break them down. They are not interchangeable, and their matching logic is distinct.
android:path
: This is for an exact match. It must match the entire path of the URL precisely. It's the most restrictive and least flexible option. Use it for specific, unchanging URLs like/login
or/about-us
.android:pathPrefix
: This matches the beginning of the URL's path. If you specify/user/
, it will match/user/123
and/user/settings/privacy
, but not/users/
(plural). It's a good middle-ground for entire sections of your site.android:pathPattern
: This is the most powerful and most misunderstood. It's not a full-blown Regular Expression engine! It uses a very simple wildcard syntax. The two special characters are:.
(period): Matches any single character.*
(asterisk): Matches the preceding character zero or more times.
.*
, which effectively means "match any sequence of characters." For example,/product/.*
will match any URL that starts with/product/
.
To make this crystal clear, here’s a comparison table. Assume our host is www.yourapp.com
.
Manifest Attribute | Example URL | Matches? | Reason |
---|---|---|---|
android:path="/profile" |
.../profile | ✅ Yes | Exact match. |
android:path="/profile" |
.../profile/edit | ❌ No | Not an exact match. |
android:pathPrefix="/orders/" |
.../orders/12345 | ✅ Yes | Path starts with the prefix. |
android:pathPrefix="/orders/" |
.../orders | ❌ No | Missing the trailing slash. The prefix must match exactly. |
android:pathPattern="/item/.*" |
.../item/abc-def | ✅ Yes | The .* wildcard matches `abc-def`. |
android:pathPattern="/item/.*" |
.../item/ | ✅ Yes | The .* wildcard matches an empty string. |
android:pathPattern="/user/.*/settings" |
.../user/123/settings | ✅ Yes | The .* wildcard matches `123`. |
Pro Tip: When in doubt, start with pathPattern
and the .*
wildcard. It offers the most flexibility for dynamic content like product IDs or usernames. Just be careful to escape any literal periods or asterisks in your path with a backslash (e.g., \.
).
Tip 2: Your `assetlinks.json` Is a Common Point of Failure
The android:autoVerify="true"
attribute in your manifest is a promise. It tells the Android OS: "Hey, I own this domain. Please verify it for me so you can skip the disambiguation dialog and open links directly in my app." The OS fulfills this verification by checking for a Digital Asset Links file on your domain.
This file, named assetlinks.json
, is a simple JSON file that creates a trusted association between your website and your app. If it's missing, malformed, or inaccessible, autoVerify
will fail silently, and you'll be back to the dreaded chooser dialog.
Common `assetlinks.json` Mistakes
Your
assetlinks.json
file must be publicly accessible, served over HTTPS without redirects, and have the correct content type. Any deviation will cause verification to fail.
- Incorrect Location: The file must be located at exactly
https://your.domain/.well-known/assetlinks.json
. Not in a subfolder, not at the root. - Server Configuration: Your server must serve the file with a
Content-Type
ofapplication/json
. Also, it must be accessible without any redirects (HTTP 301 or 302). The Android verifier will not follow them. - Wrong SHA-256 Fingerprint: This is the most common and frustrating error. The fingerprint in your
assetlinks.json
must match the certificate used to sign the exact version of the app installed on the device. A debug build has a different fingerprint than your release build signed by the Google Play Store.
To get the correct SHA-256 fingerprint for your production app, use the one provided by Google Play Console under Setup > App integrity. For local testing with a debug build, you can generate it with this command:
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android
Verifying Your `assetlinks.json`
Don't guess! Use Google's official Statement List Generator and Tester. You can input your domain and app package name, and it will check your live assetlinks.json
file and tell you if it's valid and if the association is successful. This tool is invaluable for diagnosing server-side issues.
Tip 3: The `adb` Shell Is Your Best Friend for Debugging
You don't have to rely on physically clicking links in other apps to test your implementation. The Android Debug Bridge (adb
) is a powerful command-line tool that gives you precise control for testing and diagnosis.
Simulating a Link Click
You can simulate a user clicking a link directly from your terminal. This is the fastest way to test if your manifest's intent filter is correctly configured to catch a specific URL pattern.
adb shell am start -a android.intent.action.VIEW -d "https://www.yourapp.com/product/123-abc"
If your app opens directly to the correct screen, your intent filter is working! If the browser opens or a chooser dialog appears, the OS doesn't see your app as a valid handler for that specific URL.
Checking Domain Verification Status
So, why isn't the OS seeing your app as a valid handler? The `autoVerify` might have failed. You can ask the system directly about the verification status for your app. The command varies slightly by Android version, but this one is broadly compatible:
# For newer Android versions (12+)
adb shell dumpsys domain-verification
# For older versions, this can also provide clues
adb shell dumpsys package d
In the output, search for your package name (e.g., com.yourapp.example
). You will see the domains associated with your app and their status. Look for your domain (e.g., www.yourapp.com
). The status should be verified
or 1024
. If it's ask
, none
, or anything else, it means automatic verification has failed, and you should re-check your assetlinks.json
file (Tip #2).
Resetting the App Link State
Sometimes, Android caches a failed verification attempt. Even after you've fixed your assetlinks.json
file, the device might not re-check it immediately. You can force the system to forget the old state and re-evaluate your app's links.
First, clear any user preference that might have been set:
adb shell pm set-app-link your.package.name ask
Then, reinstall your app. Upon reinstallation, the OS should attempt to re-verify your domain. After a few moments, run the `dumpsys` command again to check if the status has changed to verified
.
Conclusion: Linking It All Together
Fixing broken Universal Pattern Links in Android doesn't have to be a nightmare. By systematically approaching the problem, you can save hours of frustration. Remember the three key pillars of a successful implementation:
- The Manifest: Use the correct
path
,pathPrefix
, orpathPattern
attribute for your URL structure. When in doubt,pathPattern
with.*
is your most flexible tool. - The Server: Ensure your
assetlinks.json
is perfectly formatted, located at/.well-known/assetlinks.json
, and contains the correct SHA-256 fingerprint for your signing certificate. - The Debugging: Leverage the
adb
shell to simulate link clicks, inspect the system's domain verification status, and reset link handling policies to force re-verification.
Mastering these three areas will not only solve your current deep linking woes but also empower you to build the seamless, integrated user experiences that make native apps feel truly magical. Happy coding!