Android App Links Guide
Complete step-by-step guide to implement, configure, and test Android App Links in your mobile application
Table of Contents
1. Overview & Prerequisites
What are Android App Links?
Android App Links are HTTP URLs that bring users directly to specific content in your Android app. They provide a seamless user experience by opening your app instead of a web browser when users click on links.
Types of Deep Links
Deep Links
Basic URI schemes that can open your app
myapp://product/123
Web Links
HTTP/HTTPS URLs that show intent chooser
https://example.com/product/123
Android App Links
Verified HTTPS URLs that open directly
https://verified.com/product/123
- • Direct app opening from web links
- • Better user engagement and retention
- • Seamless content sharing between web and app
- • Enhanced SEO and discoverability
- • No app chooser dialog for verified domains
Prerequisites
- • Android Studio
- • Target SDK 23+
- • Kotlin or Java knowledge
- • ADB access for testing
- • Managed domain and hosting for verification files
- • Automatic HTTPS with valid SSL certificates
- • Auto-generated and hosted
/.well-known/assetlinks.json
- • Seamless Android domain verification support
2. Configure Intent Filters
Add Intent Filters to AndroidManifest.xml
Configure your app to handle specific URL patterns by adding intent filters to your target activity.
<activity android:name=".MainActivity" android:exported="true" android:launchMode="singleTop"> <!-- Standard activity intent filter --> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <!-- 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="yourdomain.com" /> </intent-filter> <!-- Multiple path patterns example --> <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="yourdomain.com" android:pathPrefix="/product" /> </intent-filter> </activity>
Intent Filter Parameters Explained
Required Elements
android:autoVerify="true"
- Enables automatic verificationACTION_VIEW
- Standard action for viewing contentBROWSABLE
- Allows access from browserDEFAULT
- Default category for implicit intents
Data Attributes
android:scheme
- URL scheme (https/http)android:host
- Domain nameandroid:pathPrefix
- URL path patternandroid:path
- Exact path matchandroid:pathPattern
- Path with wildcards (* and .)android:port
- Specific port number
3. Setup Digital Asset Links
Create assetlinks.json File
Digital Asset Links verify that your app is authorized to open links for your domain. Create this file and host it on your domain.
[{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourcompany.yourapp", "sha256_cert_fingerprints": [ "AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99" ] } }]
Get Your App's SHA256 Fingerprint
For Debug Builds
keytool -list -v \ -keystore ~/.android/debug.keystore \ -alias androiddebugkey \ -storepass android \ -keypass android
For Release Builds
keytool -list -v \ -keystore your-release-key.keystore \ -alias your-key-alias
From Google Play Console
Go to Play Console → Your App → Setup → App Integrity → App Signing → SHA-256 certificate fingerprint
Host the File
- • File must be accessible at:
https://yourdomain.com/.well-known/assetlinks.json
- • Must be served with
Content-Type: application/json
- • Must be accessible via HTTPS
- • No redirects allowed
4. Handle Links in Activity
Basic Link Handling (Kotlin)
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) handleAppLink(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) handleAppLink(intent) } private fun handleAppLink(intent: Intent) { val appLinkAction = intent.action val appLinkData: Uri? = intent.data if (Intent.ACTION_VIEW == appLinkAction && appLinkData != null) { handleIncomingLink(appLinkData) } } private fun handleIncomingLink(uri: Uri) { // Extract path and parameters val path = uri.path val queryParams = uri.queryParameterNames when { path?.startsWith("/product") == true -> { val productId = uri.getQueryParameter("id") openProduct(productId) } path?.startsWith("/dashboard") == true -> { openDashboard() } path?.startsWith("/profile") == true -> { val userId = uri.getQueryParameter("user") openProfile(userId) } else -> { // Handle unknown path - maybe show home screen showHome() } } } private fun openProduct(productId: String?) { productId?.let { // Navigate to product screen val intent = Intent(this, ProductActivity::class.java) intent.putExtra("product_id", it) startActivity(intent) } } private fun openDashboard() { // Navigate to dashboard val intent = Intent(this, DashboardActivity::class.java) startActivity(intent) } private fun openProfile(userId: String?) { userId?.let { // Navigate to profile screen val intent = Intent(this, ProfileActivity::class.java) intent.putExtra("user_id", it) startActivity(intent) } } private fun showHome() { // Show default home content // Already in MainActivity, just ensure we're showing home fragment } }
Advanced Routing with Navigation Component
class MainActivity : AppCompatActivity() { private lateinit var navController: NavController override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val navHostFragment = supportFragmentManager .findFragmentById(R.id.nav_host_fragment) as NavHostFragment navController = navHostFragment.navController handleAppLink(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) setIntent(intent) handleAppLink(intent) } private fun handleAppLink(intent: Intent) { val uri = intent.data uri?.let { when (it.path) { "/product" -> { val productId = it.getQueryParameter("id") val bundle = Bundle().apply { putString("productId", productId) } navController.navigate(R.id.productFragment, bundle) } "/dashboard" -> { navController.navigate(R.id.dashboardFragment) } "/profile" -> { val userId = it.getQueryParameter("user") val bundle = Bundle().apply { putString("userId", userId) } navController.navigate(R.id.profileFragment, bundle) } } } } }
🔒 Security Considerations
Input Validation
Always validate and sanitize incoming URL data to prevent security vulnerabilities.
private fun handleIncomingLink(uri: Uri) { val path = uri.path val productId = uri.getQueryParameter("id") // ❌ Bad: Direct usage without validation // openProduct(productId) // ✅ Good: Validate input first if (path != null && isValidPath(path)) { productId?.let { id -> if (isValidProductId(id)) { openProduct(id) } else { Log.w("AppLinks", "Invalid product ID: $id") showHome() } } } else { Log.w("AppLinks", "Invalid or null path: $path") showHome() } } private fun isValidProductId(id: String): Boolean { return id.matches(Regex("^[a-zA-Z0-9_-]{1,50}$")) } private fun isValidPath(path: String): Boolean { val allowedPaths = listOf("/product", "/dashboard", "/profile") return allowedPaths.any { path.startsWith(it) } }
Authentication & Authorization
- • Always check user authentication before opening sensitive content
- • Validate user permissions for accessed resources
- • Don't expose sensitive data through URL parameters
- • Use secure tokens for authenticated deep links
- • Implement rate limiting for link processing
Best Security Practices
HTTPS Only
Always use HTTPS for App Links to prevent man-in-the-middle attacks and ensure data integrity.
Certificate Pinning
Consider implementing certificate pinning for critical domains to prevent certificate-based attacks.
Minimal Permissions
Only request necessary permissions and avoid exposing sensitive app functionality through deep links.
Error Handling
Implement proper error handling and logging without exposing sensitive information to attackers.
6. Testing & Debugging
ADB Testing Commands
Test App Link Opening
adb shell am start \ -W -a android.intent.action.VIEW \ -d "https://yourdomain.com/product?id=123" \ com.yourcompany.yourapp
Check Domain Verification Status
adb shell pm get-app-links com.yourcompany.yourapp
Reset Domain Verification
adb shell pm set-app-links --package com.yourcompany.yourapp 0 yourdomain.com
Verification Status Codes
- verified - Domain verification successful
- none - No verification attempted
- ask - User needs to approve
- denied - User denied permission
- always_ask - Always show chooser
- legacy_failure - Verification failed
Debugging Tips
- • Use Android Studio's logcat to monitor intent data
- • Test with both debug and release builds
- • Verify assetlinks.json is accessible in browser
- • Check network connectivity during verification
- • Use Google's Digital Asset Links API to validate your file
7. Common Issues & Solutions
❌ App doesn't open when clicking links
- • Check if assetlinks.json is properly hosted and accessible
- • Verify SHA256 fingerprint matches your app's signing certificate
- • Ensure android:autoVerify="true" is set in intent filters
- • Test domain verification status with ADB commands
⚠️ Verification fails
- • Verify assetlinks.json returns Content-Type: application/json
- • Check for any redirects - they're not allowed
- • Ensure file is served over HTTPS
- • Validate JSON syntax with online tools
ℹ️ Browser chooser still appears
- • Domain verification may be in progress (can take up to 20 seconds)
- • Clear app defaults in Settings → Apps → Your App → Open by default
- • Reset verification status and wait for re-verification
- • Check if other apps also handle the same domain
✅ Best Practices
- • Always test on physical devices, not just emulators
- • Include both debug and release certificate fingerprints during development
- • Set launchMode="singleTop" to handle new intents properly
- • Implement proper fallback handling for unrecognized URLs
- • Use HTTPS for all domain URLs
📱 Example Android App
Want to see a working implementation? Check out our complete Android App Links example application with full source code.
Android App Links Example
Complete working Android application demonstrating both custom scheme deep links and Android App Links implementation.
1. Clone the repository: git clone https://github.com/TedyHub/android-app-links-example.git
2. Open in Android Studio and run: ./gradlew installDebug
3. Test with ADB: adb shell am start -a android.intent.action.VIEW -d "example://link/123"