Mobile Dev

Fix Android App Links Exclusions in assetlinks.json

Troubleshoot Android App Links exclusions: validate your assetlinks.json, confirm manifest intent-filter scope, and order dynamic_app_link_components correctly for Android 15.

1 answer 1 view

Why are my Android App Links exclusion rules not working? I’m trying to exclude mydomain.com/pages/login.aspx and all URLs under mydomain.com/CMS/ from opening in the app, but the app links still open these URLs. My assetlinks file is configured as follows:

json
[
  {
    "relation": [
      "delegate_permission/common.handle_all_urls"
    ],
    "target": {
      "namespace": "android_app",
      "package_name": "com.csfw.dev.azure",
      "sha256_cert_fingerprints": [
        "A8:05:3D:A9:F7:EA:81:26:96:1B:8D:AB:64:DA:7F:67:03:20:0C:EB:A7:5A:97:21:69:AD:3C:0B:C2:28:76:8A"
      ]
    },
    "relation_extensions": {
      "delegate_permission/common.handle_all_urls": {
        "dynamic_app_link_components": [
          {
            "/": "/pages/login.aspx",
            "exclude": true
          },
          {
            "/": "/CMS/*",
            "exclude": true
          },
          {
            "/": "*"
          }
        ]
      }
    }
  }
]

Testing on Android 15, adding {"/" : "*", "exclude": true} opens all URLs in the browser as expected. What am I doing wrong?

Android App Links exclusion rules may fail due to manifest intent-filter scope limitations, incorrect path syntax, rule ordering issues, or caching problems. Your assetlinks.json syntax appears correct, but dynamic rules require both proper host declarations in your app’s manifest and precise path patterns that match the URLs being tested. The exclude: true flag only works if the URL first matches your app’s declared intent-filter scope, and Android 15+ devices must be used for dynamic rules to take effect.

Contents

Understanding Android App Links Exclusions

Android App Links use assetlinks.json to establish trust between your app and website. Starting with Android 15, dynamic rules in dynamic_app_link_components allow server-side control over URL routing, including exclusions. Exclusion rules prevent specific URLs from opening in your app and redirecting to the browser instead. However, these rules only apply after the system verifies that the URL matches your app’s intent-filter scope declared in the manifest. If the manifest doesn’t cover the hostname or path, the dynamic rule is never evaluated.

Common Reasons Exclusion Rules Fail

Several factors can break exclusion functionality:

1. Manifest Intent-Filter Scope Limitations

Dynamic rules cannot override the intent-filter scope declared in your app’s manifest. If your manifest only declares android:host="mydomain.com" without path declarations, the system assumes all paths under that host are app-eligible. The exclusion rules won’t trigger for paths like /pages/login.aspx because the system never considers them for app handling in the first place. The official Android documentation states: “Dynamic rules cannot expand the scope of the URL rules that you declare statically in your app manifest.”

2. Path Syntax Issues

Path patterns in dynamic_app_link_components require exact formatting:

  • Must start with /
  • Wildcards like * only work at the end of path segments (e.g., /CMS/* is valid, but */login isn’t)
  • Paths are case-sensitive and must match the URL exactly

Your patterns /pages/login.aspx and /CMS/* are syntactically correct, but ensure they match the actual URLs without trailing slashes or case mismatches.

3. Rule Ordering Problems

Rules are evaluated sequentially, and the first match wins. Your configuration correctly places specific exclusions before the wildcard rule. However, if earlier rules accidentally match (e.g., a broader pattern like * appears before specific exclusions), they override later rules. Google’s troubleshooting guide emphasizes: “Verify rule order in dynamic_app_link_components — more specific rules must precede broader ones.”

4. Caching and Deployment Issues

Devices cache assetlinks.json aggressively. Changes may not reflect immediately, especially on older Android versions. The Android Developers blog notes: “Older Android versions ignore dynamic_app_link_components entirely. Only Android 15+ devices support these features.”

Correcting assetlinks.json Syntax

Your JSON structure is mostly correct, but ensure these details:

json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.csfw.dev.azure",
      "sha256_cert_fingerprints": [
        "A8:05:3D:A9:F7:EA:81:26:96:1B:8D:AB:64:DA:7F:67:03:20:0C:EB:A7:5A:97:21:69:AD:3C:0B:C2:28:76:8A"
      ]
    },
    "relation_extensions": {
      "delegate_permission/common.handle_all_urls": {
        "dynamic_app_link_components": [
          {
            "/": "/pages/login.aspx",
            "exclude": true
          },
          {
            "/": "/CMS/*",
            "exclude": true
          },
          {
            "/": "*"
          }
        ]
      }
    }
  }
]

Critical checks:

  • No trailing commas: Ensure no comma after the last rule object
  • Wildcard placement: Confirm "/": "*" (without exclusion) appears after specific exclusions
  • File serving: Must be served from https://mydomain.com/.well-known/assetlinks.json with Content-Type: application/json
  • JSON validity: Remove any UTF-8 BOM (Byte Order Mark) that might cause parsing errors. Stack Overflow reports that BOMs cause “malformed JSON” errors even when the file appears valid.

Verifying Manifest Intent-Filters

The most common fix is updating your app’s manifest to explicitly declare the paths you want to exclude. Add these intent-filter components:

xml
<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="mydomain.com"
          android:pathPrefix="/pages" />
    <data android:scheme="https"
          android:host="mydomain.com"
          android:pathPrefix="/CMS" />
</intent-filter>

This tells Android that URLs under these paths are app-eligible, allowing the dynamic exclusions to take effect. Without these declarations, the system bypasses your assetlinks.json rules entirely. The Android documentation explains: “Exclusions are applied after the system matches a URL to the app’s intent-filter scope. If the manifest intent-filter doesn’t cover the URL, the system never reaches the dynamic rule.”

Testing and Debugging Steps

  1. Clear caches:

    bash
    adb shell pm clear com.google.android.gms
    adb shell pm clear com.csfw.dev.azure
    
  2. Check logs during URL testing:

    bash
    adb logcat -s AppLinks
    
  3. Verify assetlinks.json:

    bash
    curl -v https://mydomain.com/.well-known/assetlinks.json
    

    Confirm it returns HTTP 200 with Content-Type: application/json and no BOM.

  4. Test with Android 15+ devices only. Older versions ignore dynamic rules.

Real-World Troubleshooting Checklist

Issue Solution
Manifest doesn’t declare excluded paths Add android:pathPrefix entries for /pages and /CMS
Path syntax errors Ensure leading / and proper wildcard placement
Rule ordering problems Place specific exclusions before wildcard rules
JSON/BOM issues Remove BOM and validate JSON syntax
Caching Clear app cache and Google Play services cache
Android version Test only on Android 15+ with Google Play services
Serving errors Verify HTTPS, correct Content-Type, and file location

Example Working Configuration

Here’s a full working assetlinks.json for excluding /pages/login.aspx and /CMS/*:

json
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.csfw.dev.azure",
      "sha256_cert_fingerprints": [
        "A8:05:3D:A9:F7:EA:81:26:96:1B:8D:AB:64:DA:7F:67:03:20:0C:EB:A7:5A:97:21:69:AD:3C:0B:C2:28:76:8A"
      ]
    },
    "relation_extensions": {
      "delegate_permission/common.handle_all_urls": {
        "dynamic_app_link_components": [
          {
            "/": "/pages/login.aspx",
            "exclude": true
          },
          {
            "/": "/CMS/*",
            "exclude": true
          },
          {
            "/": "*"
          }
        ]
      }
    }
  }
]

FAQ

Q: Why does "/": "*", "exclude": true work but not my specific exclusions?
A: The wildcard exclusion bypasses manifest scope requirements by explicitly excluding all URLs. Specific exclusions require the paths to be declared in the manifest first.

Q: Can I exclude query parameters?
A: Yes, add "query": "your=param" to rule objects. Query parameter matching is exact and case-sensitive.

Q: How long do changes take to propagate?
A: Up to 24 hours due to device caching. Test immediately after clearing caches.

Conclusion

Android App Links exclusions fail primarily when the app’s manifest doesn’t declare the paths being excluded or when JSON syntax errors prevent proper rule evaluation. Always verify manifest scope, validate JSON formatting, test on Android 15+, and clear device caches. The exclusion logic works reliably when the system first recognizes a URL as app-eligible through intent-filters before applying your server-side rules. For persistent issues, check the official Android documentation and use adb logs for real-time debugging.

Sources

Authors
Verified by moderation
Moderation
Fix Android App Links Exclusions in assetlinks.json