Android Instant Apps (now called Google Play Instant) let users run your app from a URL without installing it. The user taps a link, a lightweight version of your app loads in seconds, and they interact with it immediately. If they like it, they can install the full app. The deep link URL that triggered the instant experience carries over.
This guide covers how to build Instant App experiences powered by deep links. For the iOS equivalent, see deep linking and App Clips. For Android App Links setup, see Android Instant Apps and deep linking.
How Instant Apps Work with Deep Links
The URL-to-Experience Flow
User taps link: https://yourapp.com/products/running-shoes
→ Android checks: is the full app installed?
→ Yes: open the full app via App Links
→ No: is Google Play Instant enabled for this URL?
→ Yes: load the Instant App module (~15MB)
→ Instant App opens to the product page
→ No: open the URL in Chrome
The same URL handles all three cases. App Links verification is required for all of them.
Instant App Module Structure
An Instant App is split into feature modules. Each module handles specific URLs:
app/
├── base/ (shared code, always loaded)
├── feature-products/ (handles /products/* URLs, ~5MB)
├── feature-checkout/ (handles /checkout/* URLs, ~4MB)
└── feature-account/ (handles /account/* URLs, ~3MB)
When a user taps a /products/running-shoes link, only the base and feature-products modules load. The checkout module loads when the user navigates to checkout.
Setting Up Instant App Deep Links
App Links Verification
Instant Apps require verified App Links. The assetlinks.json file must include the Instant App package:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.yourapp",
"sha256_cert_fingerprints": [
"YOUR_RELEASE_FINGERPRINT",
"YOUR_INSTANT_APP_FINGERPRINT"
]
}
}]
Manifest Configuration
Each feature module declares its URL patterns in the manifest:
<!-- feature-products/src/main/AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:dist="http://schemas.android.com/apk/distribution"
package="com.yourapp.feature.products">
<dist:module
dist:instant="true"
dist:title="Products">
<dist:delivery>
<dist:install-time />
</dist:delivery>
</dist:module>
<application>
<activity
android:name=".ProductActivity"
android:exported="true">
<intent-filter android:autoVerify="true"
android:order="1">
<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="yourapp.com"
android:pathPrefix="/products/" />
</intent-filter>
<meta-data
android:name="default-url"
android:value="https://yourapp.com/products" />
</activity>
</application>
</manifest>
Handling the Deep Link in the Activity
class ProductActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent.data ?: run {
showDefaultProducts()
return
}
val productSlug = uri.lastPathSegment
val variant = uri.getQueryParameter("variant")
val campaign = uri.getQueryParameter("utm_campaign")
viewModel.loadProduct(productSlug, variant)
// Track that this came from an instant app experience
if (isInstantApp()) {
analytics.trackInstantAppView(productSlug, campaign)
}
}
private fun isInstantApp(): Boolean {
return packageManager.isInstantApp
}
}
Transitioning from Instant to Installed
Install Prompt
When the user wants to do something the Instant App cannot handle (e.g., create an account, access offline features), prompt them to install:
fun promptInstall() {
val intent = Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse("https://play.google.com/store/apps/details?id=com.yourapp")
setPackage("com.android.vending")
putExtra("callerId", packageName)
putExtra("referrer", "utm_source=instant_app&utm_medium=install_prompt")
}
startActivity(intent)
}
Preserving State Through Install
Use the Cookie API to transfer data from the Instant App to the installed app:
import com.google.android.gms.instantapps.PackageManagerCompat
// Instant App: save state before install
fun saveStateForInstall(userId: String, cartItems: List<CartItem>) {
val cookie = JSONObject().apply {
put("userId", userId)
put("cart", JSONArray(cartItems.map { it.toJSON() }))
put("lastViewedProduct", currentProductSlug)
}.toString().toByteArray()
PackageManagerCompat.setInstantAppCookie(this, cookie)
}
// Installed app: restore state after install
fun restoreFromInstantApp() {
val cookie = PackageManagerCompat.getInstantAppCookie(this)
if (cookie.isNotEmpty()) {
val state = JSONObject(String(cookie))
val userId = state.optString("userId")
val lastProduct = state.optString("lastViewedProduct")
if (userId.isNotEmpty()) {
authManager.restoreSession(userId)
}
if (lastProduct.isNotEmpty()) {
navigateToProduct(lastProduct)
}
// Clear the cookie after restoring
PackageManagerCompat.setInstantAppCookie(this, ByteArray(0))
}
}
Deep Link Continuity
After installation, the same URLs that triggered the Instant App now open the installed app. No URL changes needed. The App Links verification covers both instant and installed versions.
Size Optimization
The 15MB Limit
Each Instant App module must be under 15MB (including the base module). Strategies to stay within the limit:
- Move large assets to a CDN. Download images, 3D models, and videos on demand.
- Use dynamic feature modules. Split features so only the needed code loads.
- Minimize dependencies. Each library adds to the module size.
- Use WebP images. Smaller than PNG/JPEG for the same quality.
- Enable ProGuard/R8. Remove unused code and resources.
// build.gradle - enable code shrinking
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
}
}
}
Feature Module Boundaries
Design your modules around URL patterns:
| Module | URLs | Size Budget |
|---|---|---|
| base | (always loaded) | ~5MB |
| products | /products/* | ~4MB |
| search | /search* | ~3MB |
| checkout | /checkout/* | ~3MB |
Instant Apps and Attribution
UTM Parameters
UTM parameters in the deep link URL pass through to the Instant App:
https://yourapp.com/products/shoes?utm_source=email&utm_campaign=summer&utm_medium=link
Track these in the Instant App to measure which channels drive instant app experiences.
Install Referrer
When the user installs from the Instant App, the install referrer can carry context:
// Include referrer when prompting install
val referrer = "utm_source=instant_app&utm_content=${currentProductSlug}"
val playStoreUri = Uri.parse(
"https://play.google.com/store/apps/details?id=com.yourapp&referrer=$referrer"
)
Tolinku and Instant Apps
Tolinku handles App Links verification for both installed and Instant App versions. Configure your routes in the Tolinku dashboard, and the same assetlinks.json serves both app modes. The web fallback page shows for users on non-Android devices or when the Instant App module is not available.
For more on deep linking trends, see the future of mobile deep linking. For App Clips (the iOS equivalent), see deep linking and App Clips.
Get deep linking tips in your inbox
One email per week. No spam.