Android App Links let your app automatically handle URLs for your domain. Unlike deep links (which show an app chooser), App Links open your app directly with verified domain ownership.
Key Takeaways
- 1Host an assetlinks.json file on your web server
- 2Add intent filters with autoVerify="true" in AndroidManifest.xml
- 3Handle incoming URLs in your Activity
- 4Works with standard https:// URLs—no custom schemes needed
- 5Falls back to browser if app isn't installed
Deep Links vs App Links
Android supports two types of deep linking: traditional deep links using custom schemes, and verified App Links using HTTPS. Understanding the difference helps you choose the right approach for your use case.
| Feature | Deep Links | App Links |
|---|---|---|
| URL schemes | Custom (myapp://) | https:// only |
| Disambiguation | Shows app chooser | Opens directly |
| Verification | None | Domain ownership verified |
| Fallback | None (fails if no app) | Opens in browser |
App Links are the better choice for most production apps because they provide a seamless user experience without the disambiguation dialog. Let's set them up.
Digital Asset Links File
The Digital Asset Links file proves you own both the domain and the app. It contains your app's package name and signing certificate fingerprint. Host this JSON file at /.well-known/assetlinks.json.
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5"
]
}
}
]The sha256_cert_fingerprints array contains your app's signing certificate fingerprint. This proves your app is genuinely published by you, not a malicious clone.
Getting Your SHA256 Fingerprint
You need to extract the SHA256 fingerprint from your signing keystore. The fingerprint differs between debug and release builds, so you will need both for development and production.
# Debug keystore
keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android
# Release keystore
keytool -list -v -keystore your-release-key.keystore -alias your-alias
# From Google Play Console
# Setup → App signing → SHA-256 certificate fingerprintIf you use Google Play App Signing, get the release fingerprint from the Play Console. The debug fingerprint comes from your local debug keystore, which Android Studio creates automatically.
Asset Links Requirements
Like the iOS AASA file, the assetlinks.json file must be served correctly for Android to accept it. Pay attention to these requirements when deploying.
| Requirement | Details |
|---|---|
| Location | /.well-known/assetlinks.json |
| Content-Type | application/json |
| HTTPS | Must be served over HTTPS |
| No redirects | File must be served directly |
| Fingerprints | Include both debug and release certificates |
With the server-side file in place, configure your Android app to declare which URLs it handles using intent filters.
AndroidManifest.xml Configuration
Intent filters in your AndroidManifest.xml tell Android which URLs your app can handle. The autoVerify="true" attribute triggers the verification process that downloads and checks your assetlinks.json file.
<activity
android:name=".MainActivity"
android:exported="true">
<!-- App Links intent filter -->
<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="example.com"
android:pathPrefix="/products" />
</intent-filter>
<!-- Additional paths -->
<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="example.com"
android:pathPrefix="/users" />
</intent-filter>
</activity>Each intent filter handles a specific set of paths. You can add multiple intent filters to handle different URL patterns. The scheme must be https for App Links verification to work.
Path Patterns
Android provides three ways to match URL paths. Choose based on how specific you need to be—exact matches for specific pages, prefixes for sections, and patterns for complex routing.
| Attribute | Description | Example |
|---|---|---|
path | Exact path match | /products/featured |
pathPrefix | Prefix match | /products (matches /products/*) |
pathPattern | Pattern with wildcards | /products/.* (regex-like) |
Handling Links in Your App
When a user taps an App Link, Android launches your Activity with an Intent containing the URL. You need to handle this in both onCreate (cold start) and onNewIntent (app already running).
Kotlin
In a traditional Activity-based app, extract the URL from the Intent and route to the appropriate screen. The example below shows a common pattern using a when expression for path-based routing.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handleIntent(intent)
}
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
handleIntent(intent)
}
private fun handleIntent(intent: Intent) {
val action = intent.action
val data = intent.data
if (action == Intent.ACTION_VIEW && data != null) {
handleDeepLink(data)
}
}
private fun handleDeepLink(uri: Uri) {
when {
uri.path?.startsWith("/products/") == true -> {
val productId = uri.lastPathSegment
navigateToProduct(productId)
}
uri.path?.startsWith("/users/") == true -> {
val userId = uri.lastPathSegment
navigateToUser(userId)
}
else -> {
// Handle unknown paths
navigateToHome()
}
}
}
}The handleDeepLink function parses the URI and routes based on the path. Use lastPathSegment to extract IDs from paths like /products/123.
Jetpack Compose with Navigation
Jetpack Compose Navigation has built-in support for deep links. Declare the URL pattern directly in your NavHost, and the navigation library handles the routing automatically.
@Composable
fun MyNavHost(navController: NavHostController) {
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable(
"products/{productId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/products/{productId}"
}
)
) { backStackEntry ->
val productId = backStackEntry.arguments?.getString("productId")
ProductScreen(productId)
}
composable(
"users/{userId}",
deepLinks = listOf(
navDeepLink {
uriPattern = "https://example.com/users/{userId}"
}
)
) { backStackEntry ->
val userId = backStackEntry.arguments?.getString("userId")
UserScreen(userId)
}
}
}The navDeepLink builder matches URLs against the pattern and automatically extracts path parameters. Arguments are available in the backStackEntry.arguments bundle.
Testing App Links
Testing App Links requires verifying both the server configuration and the Android setup. Use these ADB commands to trigger links and check verification status without needing to tap links manually.
# Verify assetlinks.json is accessible
curl -I https://example.com/.well-known/assetlinks.json
# Trigger a link via ADB
adb shell am start -W -a android.intent.action.VIEW \
-d "https://example.com/products/123" \
com.example.app
# Check verification status
adb shell pm get-app-links com.example.app
# Reset verification (for testing)
adb shell pm set-app-links --package com.example.app 0 allThe pm get-app-links command shows verification status. If it says "verified", your App Links are working. If not, check the troubleshooting section below.
Troubleshooting
App Link verification can fail for several reasons. These are the most common issues and how to resolve them.
Verification failed
Check SHA256 fingerprint matches, assetlinks.json is accessible, and uses correct package name.
App chooser still showing
Ensure autoVerify="true" is set. Check that all intent-filter hosts are verified.
Works on debug but not release
Include both debug and release certificate fingerprints in assetlinks.json.
Multiple Apps or Build Variants
If you have multiple apps or build variants (debug, staging, release), you need to include all of them in your assetlinks.json. Each app-package combination needs its own entry with the corresponding certificate fingerprint.
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app",
"sha256_cert_fingerprints": [
"AA:BB:CC:...",
"DD:EE:FF:..."
]
}
},
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.example.app.debug",
"sha256_cert_fingerprints": [
"11:22:33:..."
]
}
}
]This example shows a production app and a debug variant. The debug version has a different package name suffix and uses the debug keystore fingerprint. Both can handle links to the same domain.
Try the URL Builder
Use our Android Deep Link template to explore deep link URL patterns. You can test different path structures and see how they would map to your app's navigation.