01 · Configure the intent
Deep link URL
Target component (optional, leave blank to let Android pick)
Intent action & categories
Intent extras (passed via --es / --ei / --ez / --ef / --el)
am flags
02 · Generated command


            

What these flags actually do

The am ("activity manager") subcommand of adb shell is how you invoke an Android Intent from your laptop. Every flag below maps onto a property of the Intent object your activity receives in onCreate or onNewIntent. There's no magic. Once you've written the equivalent Kotlin a few times, the flag syntax stops feeling cryptic.

What you're invoking: the action

-a <action> defines what kind of operation you're trying to perform, not where. For deep links, this is overwhelmingly android.intent.action.VIEW, which translates to "show this thing to the user." Sharing is SEND, editing flows are EDIT, telephony is DIAL or CALL. The most common testing mistake is omitting the action entirely. The default is MAIN, which doesn't match deep-link intent filters, so you end up at the app's launcher activity instead of your handler and you can't figure out why your "deep link test" is broken.

What you're invoking on: the data URI

-d <data URI> is the URL itself, the thing your <intent-filter> matches against. Always wrap it in double quotes. The shell otherwise treats &, ?, and # as control characters and your URL gets truncated silently. If you see only part of your query string arriving in the app, this is almost always the reason. The "silently" is the killer, because the command appears to succeed.

Which app gets the intent: targeting flags

Two flags control resolution. -n <package/activity> bypasses Android's intent resolution entirely and sends the intent to one specific Activity. That's useful when you want to skip the disambiguation dialog and target a particular entry point, like testing a deep-link activity that isn't your default launcher. The format is either relative (com.example.app/.MainActivity) or fully qualified (com.example.app/com.example.app.MainActivity); either works.

-p <package> is less aggressive: it lets Android resolve the intent normally, but only among intent filters declared by the named package. This is the one to remember if you remember nothing else. It's how you verify your app handles an HTTPS universal link instead of Chrome winning the resolution race because your App Links aren't auto-verified yet. Use -p for "did my app handle this correctly," and -n only when you specifically want to bypass resolution.

Category and extras

Almost all deep links arriving from external sources, like browsers and messaging apps, carry the BROWSABLE category. If you skip -c android.intent.category.BROWSABLE during testing, you may match an intent filter that wouldn't actually match in production: your app appears to handle the deep link locally but breaks when a real user taps the link in Chrome. Default to BROWSABLE on; turn it off only if you have a specific reason, like testing an internal share-target activity that doesn't declare it.

The extras flags pass typed key-value pairs into the intent: --es for strings, --ei for ints, --ez for booleans, --ef for floats, --el for longs. These map onto intent.getStringExtra("key") and friends. Worth knowing: deep-link query parameters are not automatically passed as extras. They live inside intent.getData() and your activity extracts them via intent.getData().getQueryParameter("foo"). If you actually need them as extras, you have to set them explicitly, either with --es on the am command line or by reading-then-writing inside the receiving activity.

Process and timing control

-W makes the command synchronously wait for the activity launch to complete, and prints timing info when it does. It's almost always worth including while testing. Without it, am returns the moment the launch is dispatched, not the moment your activity actually appears, so failures look like successes and you don't see the error log until much later. The cold-start timing data -W reports is also useful for performance work, though for serious profiling you'd use other tools.

-S force-kills the app before launching the deep link. The reason to care: if your app is already running, the deep link arrives via onNewIntent; with -S it arrives via onCreate. These are two different code paths in your app, and countless deep-link bugs only manifest in one or the other. If you're not testing both, you're shipping a bug you haven't found yet.

-D pauses the app at launch until a debugger attaches. Pair it with Android Studio's Run → Attach Debugger workflow when you need to step through your onCreate or onNewIntent handler from the very first instruction. Mostly useful for chasing a bug that only manifests on cold start. By the time you'd normally attach the debugger after launch, the interesting code has already run.

The user profile flag, briefly

--user <id> targets a specific user profile on a multi-user device. User 0 is the primary owner; 10 and above are work profiles, secondary users, and the like. Most developers never need this. If you support enterprise managed profiles, you'll eventually hit a bug that only reproduces in one specific profile, and this is how you reach it from the command line.

Why am start lies

The single most expensive thing to learn about am start is that "success" exit code doesn't mean what you think. The default behaviour is fire-and-forget: am hands the intent to ActivityManager and returns 0, regardless of whether an Activity ever actually launched. If your app crashes during onCreate, if no Activity matches the intent, or if the intent is dispatched to a different app than the one you expected, am start still prints Starting: Intent { ... } and exits zero. You see green and move on, then spend forty minutes wondering why your breakpoint never hit.

Three things help. First, always test with -W. The wait flag turns am synchronous and prints a small status block: Status: ok / Activity: com.example.app/.MainActivity / ThisTime: 412 / TotalTime: 412. If the activity name in the output doesn't match what you expected, you've hit an intent resolution bug rather than a code bug. Second, run adb logcat -d -t 200 *:E ActivityManager:I immediately after. ActivityManager logs real-time intent dispatch decisions and any ActivityNotFoundException shows up there before am even returns. Third, when chasing a "deep link doesn't trigger anything" bug, install with adb install -r -t -d on a clean target. Leftover builds from yesterday's feature branch will absorb your intent silently if their intent-filter happens to match.

Recipes that come up often

Verify a universal link reaches your app, not the browser

adb shell am start -W -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://example.com/products/42" -p com.example.app. Restricting resolution to your package with -p means Android will throw rather than silently hand it to Chrome, which is exactly the failure mode you want to surface during testing. If it throws, your autoVerify isn't set up or your assetlinks.json is missing or stale.

Test the deep link path while the app is already foreground

Omit -S and the intent arrives in your existing process via onNewIntent. This is the path most apps under-test because the convenient debugging workflow always starts from a fresh launch. Half of "the app handles the link correctly the first time but the wrong screen second time" bugs live in onNewIntent handlers that forgot to call setIntent(intent) or that read stale Activity state.

Pass a typed payload alongside the URI

--es source notification --ei retry_count 3 --ez from_widget true sets a string, an int, and a boolean as Intent extras. These travel under intent.getStringExtra("source") rather than as query parameters, and they bypass any URL encoding entirely. Useful for reproducing a notification-driven deep link where the notification was building its intent in Kotlin with typed extras. A query-string-only reproduction misses the data type and order.

When ADB itself is the problem

Plenty of "deep-link bugs" turn out to be ADB connectivity issues mis-diagnosed. Three quick checks rule that out. adb devices should list exactly one device (or one emulator and one device, but you should be targeting one explicitly with -s). If a device shows as unauthorized, accept the RSA-key dialog on the device; if it shows as offline, run adb kill-server then adb start-server. If multiple devices show and your am start hits the wrong one, you'll get a successful exit but no behaviour on the device you're staring at, which is embarrassing to discover after an hour of code review.

On wireless ADB (Android 11+), the connection silently drops when the laptop suspends, when the device reboots, or when either changes network. The behaviour you'll observe is "command timed out" or "no devices/emulators found." Re-pair with adb pair host:port and the six-digit code from Settings → Developer options → Wireless debugging. Wired USB is more reliable for an active debug session; wireless is convenient for occasional one-off launches.

By Belchior · Last updated · May 2026

Quick recipes · tap to load
Copied