How to test deep links on Android and iOS: adb, simctl and real devices
The commands to test scheme links, App Links and Universal Links on emulators, simulators and phones, what each one proves, and how to test deferred deep links without publishing to a store.
Deep links fail quietly. A wrong intent filter, a stale apple-app-site-association file or a cold-start race just opens the browser or the home screen, with no error. The fix is to test every case on purpose, with commands you can repeat.
This is the reference we use: the adb and simctl commands, what each one proves, how to test on real devices, and how to test deferred deep links without publishing to a store. If a link is failing verification, our debugging checklists for iOS Universal Links and Android App Links go deeper.
What to test
Each row is a separate test, on each platform:
| Case | What should happen |
|---|---|
Custom scheme (myapp://product/42) |
The app opens on the product screen |
Verified https link (https://links.example.com/product/42) |
The app opens directly, with no browser and no chooser |
| Cold start (app not running) | The right screen, not the home screen |
| Warm start (app in the background) | The app comes forward and navigates |
| App already on another screen | Navigation still happens, once |
| App not installed | The store opens (or your web fallback) |
| First open after install | The same screen the link pointed to (deferred deep link) |
Cold start is where most bugs hide. In React Native, Linking.getInitialURL() handles cold starts and the url event handles warm ones; forgetting either breaks half the table.
Android: adb shell am start
Send the link as a browser would, with a VIEW intent in the BROWSABLE category:
adb shell am start -W -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://links.example.com/product/42"
-W waits for the launch and prints how it went. Two variations matter:
# Test your routing only: with the package name, Android only looks inside your app,
# so it opens if any intent filter matches, verified or not.
adb shell am start -W -a android.intent.action.VIEW \
-c android.intent.category.BROWSABLE \
-d "https://links.example.com/product/42" com.example.shop
# A custom scheme
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/42" com.example.shop
Leave the package name off when you want to know whether the link opens the app the way a user's tap would. Add it when you only want to test what your app does with the URL.
For a cold start, stop the app first: adb shell am force-stop com.example.shop.
Quote URLs with &. Your local shell removes the quotes before adb passes the command to the device's shell, which treats & as an operator, so -d "https://links.example.com/p?id=1&ref=x" can lose ref. Quote the whole command for the device shell:
adb shell 'am start -W -a android.intent.action.VIEW -d "https://links.example.com/p?id=1&ref=x"'
Android: check App Links verification and fix "unable to resolve Intent"
If the link opens a browser or a chooser, check verification. These commands are from Android's Verify App Links guide:
# What Android decided, per domain
adb shell pm get-app-links com.example.shop
# Reset, verify again, then wait a few minutes and check
adb shell pm set-app-links --package com.example.shop 0 all
adb shell pm verify-app-links --re-verify com.example.shop
adb shell pm get-app-links com.example.shop
You want verified next to each host. none means nothing is recorded yet (wait and check again), legacy_failure means the legacy verifier rejected it, and a number of 1024 or more is a verifier error code. The usual causes are an assetlinks.json that lists the wrong SHA-256 fingerprint (Play App Signing uses its own key) or a redirect on the file. Our assetlinks.json guide covers each one.
Error: Activity not started, unable to resolve Intent is different. It means no activity in any installed app accepts that intent. Check:
- The intent filter has
VIEW,DEFAULTandBROWSABLE, and the rightscheme,hostand path attributes. - The activity has
android:exported="true"(required for activities with intent filters when you target Android 12, API 31, or later). - You installed the build you think you did. A debug build with
applicationIdSuffix ".debug"has a different package name. - The URL in the command matches the filter exactly, including
httpversushttps.
iOS Simulator: xcrun simctl openurl
With the app installed on a booted simulator:
xcrun simctl openurl booted "https://links.example.com/product/42"
xcrun simctl openurl booted "myapp://product/42"
# Cold start: quit the app first
xcrun simctl terminate booted com.example.shop
What the simulator proves: your app's entitlement includes the domain, the association file was accepted, and your code routes the link from both cold and warm starts. It reads the association file through Apple's CDN like a device does, unless you use ?mode=developer (below).
What it doesn't prove: how the link behaves when tapped in Instagram or another app, the App Store install path, or real-device timing. Use a device for those.
iOS device: tap tests and developer mode
On an iPhone, the reliable test is a tap from an app that hands links to iOS:
- Paste the link into Notes or send it to yourself in Messages.
- Tap it. The app should open directly.
- Long-press it. You should see an option to open it in your app. If you only see Safari options, iOS doesn't think the link belongs to your app.
Don't type or paste the link into Safari's address bar. That's direct navigation and never opens an app.
Two checks that need no sysdiagnose:
- Settings → Developer → Universal Links → Diagnostics (with Developer Mode on): enter the full URL and iOS tells you whether it's valid for an installed app.
- Developer mode for associated domains: add
?mode=developerto the entry in your entitlement during development (applinks:links.example.com?mode=developer) and turn on Associated Domains Development in the Developer settings. The device then fetches the file from your server directly, so you don't wait for Apple's CDN.
Remove ?mode=developer before release. To see what the CDN has right now: curl https://app-site-association.cdn-apple.com/a/v1/links.example.com.
For ad hoc builds on testers' iPhones, our Get UDID tool finds the device ID in Safari.
React Native and Expo specifics
uri-scheme opens scheme links on a simulator or emulator. These commands are from Expo's linking guide:
npx uri-scheme open myapp://product/42 --ios
npx uri-scheme open myapp://product/42 --android
Expo Go uses the exp:// scheme and puts /--/ before the path:
npx uri-scheme open "exp://127.0.0.1:8081/--/product/42?ref=test" --ios
Expo Go can't claim your domain, so Universal Links and App Links need a development build (npx expo run:ios, npx expo run:android, or eas build --profile development). After you add or change scheme, associatedDomains or intentFilters in app.json, make a new build. Our Expo deep linking guide covers the config.
With the CodeOnAir React Native SDK, turn on logging while you test:
DeepLink.init({ debug: true });
DeepLink.onLink((link) => console.log(link.path, link.params, link.matchMethod, link.confidence, link.isDeferred));
Testing deferred deep links
A deferred deep link is the case where the app isn't installed when the link is tapped. To test it, the install has to happen after the tap:
- Uninstall the app. Uninstalling clears the SDK's stored install ID, so the next launch counts as a first open.
- Tap the link on the phone, from Notes, Messages or a chat. The store opens.
- Install and open the app within the link's attribution window.
- Check the first launch:
isDeferredshould betrue, with the link's path and parameters.
On Android, install from the Play Store (a test track works) so the install referrer carries the click ID. The match method is then install_referrer, which is exact. This needs react-native-play-install-referrer in the app.
You don't need to publish to test the rest. After tapping the link, you can install the build any way you like (Xcode, npx expo run, adb install) and open it. There's no store referrer then, so the first open is matched on signals instead (probabilistic, with a confidence score). Keep the phone on the same network for the tap and the first open, and open the app within the window: up to 1 hour on Free, 24 hours on Starter and Growth, 7 days on Scale. On iOS that's also how real users are matched unless you turn on clipboard matching; our post on deferred deep linking on iOS without the IDFA explains it.
Tap a fresh link before each test run. A click is matched to one install at most, so reusing an old tap gives no match. For the bigger picture, see what deferred deep linking is.
A copy-paste checklist
-
adb shell am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "<https link>"opens the app, with no package name -
adb shell pm get-app-links <package>showsverifiedfor every host - The same with the app force-stopped (cold start)
-
xcrun simctl openurl booted "<https link>"opens the right screen, cold and warm - On an iPhone, tapping the link in Notes opens the app; long-press shows "Open in" your app
- Scheme links open the right screen on both platforms
- Uninstalled: the link goes to the store; after install, the first open lands on the link's screen
- On desktop, the link opens your website
- In Instagram or Facebook, the link shows an "Open in app" page (why)
FAQ
Why does adb say "unable to resolve Intent"?
No installed activity accepts the intent. Check that the intent filter has VIEW, DEFAULT and BROWSABLE with the right scheme and host, that the activity is exported, and that the installed package name matches the build you meant to test.
Can I test Universal Links on the simulator?
Yes. xcrun simctl openurl booted "<https link>" opens your app if the entitlement and association file are right. It doesn't show how links behave inside other apps or after a store install, so finish on a device.
Why doesn't pasting the link into Safari's address bar open the app?
Typing or pasting a URL is direct navigation in Safari, and iOS never opens an app for it. Tap the link from Notes or Messages instead.
How do I test a deferred deep link without publishing?
Uninstall the app, tap the link on the phone, then install your build from Xcode or adb and open it on the same network within the attribution window. The first open is matched on signals. Only the Play Store install referrer needs a store install.
How do I reset App Links verification?
Run adb shell pm set-app-links --package <package> 0 all, then adb shell pm verify-app-links --re-verify <package>, wait a few minutes, and check with pm get-app-links.
Check your domain's files in one click
Most link failures start with the hosted files. The free Universal Link and App Link Validator checks apple-app-site-association and assetlinks.json the way phones fetch them: HTTPS, redirects, content type, JSON structure, whether a path matches, Apple's CDN copy and Google's Digital Asset Links verdict.
If you'd rather not host the files yourself, CodeOnAir AppLinks serves them on your link domain and adds deferred deep links. Create a free account to try it with 100 app links.
Commands checked on 27 October 2026 against Android's "Verify App Links" guide and Expo's linking guide. Run them on your own emulator, simulator and devices; tool output varies between Android and Xcode versions.