Skip to content
Language: EN

How do you declare the tags an Android app should receive?

With intent filters in the manifest, matched in a fixed order: NDEF content first, then technology lists. Filter on the content your app handles, in lower case. Two recent changes decide whether anything arrives: web links go to ACTION_VIEW from Android 16, and Android 17 requires the NFC dispatch permission on the activity.

Three levels, one winner

Two declarations come before any filter: the android.permission.NFC permission and, for an app that is useless without NFC, the android.hardware.nfc feature marked as required, which Google Play uses to hide the app from phones without the hardware.

The NFC basics guide then lists three dispatch intents from highest to lowest priority: ACTION_NDEF_DISCOVERED for a tag whose NDEF content maps to a type, ACTION_TECH_DISCOVERED when no activity handles the first, and ACTION_TAG_DISCOVERED as the last resort.

Dispatch stops at the first level where an activity matches. If several match at that level the user gets a chooser, which the Tag reference presents as the outcome the design tries to avoid, since moving the phone to choose usually breaks the connection.

The NDEF level reads one record. The same reference says the system inspects the first record of the first message, so a message that opens with a record your app ignores is, for dispatch, a message about something else.

What the first record is dispatched as
First recordDispatched as
Well known type, URIA URI built from the payload
Well known type, textThe MIME type text/plain
MIME mediaThe MIME type the record declares
Absolute URIA URI built from the type field
External typeA URI beginning with vnd.android.nfc://ext/
Empty, unknown or unchangedNothing: dispatch falls back to technologies

Filtering on content

An ACTION_NDEF_DISCOVERED filter names the action, the DEFAULT category and a data element: a MIME type, or a scheme with a host and a path.

Write every part in lower case. The IntentFilter reference states that MIME type, scheme and host matching are case sensitive, unlike the RFCs they come from. When a record is mapped to a URI, the NdefRecord reference says the scheme is lower cased and says nothing of the host, so a tag written with a capitalised host can miss a filter written in lower case.

Filter on what your app handles and nothing wider. The foreground dispatch example in the Advanced NFC guide registers every MIME type with a comment asking developers to name only the ones they need, and an over broad filter puts the app in the chooser for tags it cannot use.

The largest recent change concerns the most common tag. From Android 16, the NFC basics guide states, a tag holding an http or https link triggers ACTION_VIEW instead of ACTION_NDEF_DISCOVERED.

From Android 17, the same tag first shows an open link notification, and the link opens only when the user acts on it.

An app that filtered on its own https address through ACTION_NDEF_DISCOVERED therefore stops receiving those tags on these versions. The documentation points to Android App Links for opening an app from such a link.

The change is scoped to http and https. The documentation describes nothing similar for MIME records, external types or other schemes, which keep going through the NDEF level.

Technology lists

ACTION_TECH_DISCOVERED needs an intent filter and a meta-data entry that points to an XML resource of tech-list elements. The NfcAdapter reference spells out the logic: every technology inside one tech-list must be present, and any single tech-list is enough.

Name only classes that every target phone provides. The TagTechnology reference makes MifareClassic, MifareUltralight and NfcBarcode optional, and a phone that lacks one never lists it, so a technology list that requires it never matches on that phone, and nothing reports it.

What Android 16 and 17 require

From Android 17, an app that targets an SDK above Android 16 must protect each activity that receives NFC intents with android.permission.DISPATCH_NFC_MESSAGE, so that only the NFC system service can start it. The same passage adds that the system does not dispatch NFC intents to an app in a stopped state: never launched by the user, or force stopped.

From Android 16, users can refuse tag intents app by app. isTagIntentAppPreferenceSupported() says whether the phone offers the setting, isTagIntentAllowed() whether the user left your app allowed, and ACTION_CHANGE_TAG_INTENT_PREFERENCE opens the page where it can be changed.

And ACTION_TAG_DISCOVERED, the catch all level, is deprecated from Android 17, which leaves content filters and technology lists as the two supported ways in.

Frequently asked questions

Why does my NDEF filter never fire?

Check the case of the MIME type, scheme and host, then the first record on the tag. On Android 16 and later, check whether the tag holds a web link, and on Android 17 whether the activity carries the dispatch permission.

Why does a chooser appear when my app is installed?

Another app matches at the same level. Narrow your filter to content only your app handles, or name your app on the tag with an Android Application Record.

Does an Android Application Record replace intent filters?

No. The system tries the intent filters first. If the matching activity does not belong to the named app, or several activities match, it starts the named app instead, and opens Google Play when that app is not installed.

Can my app still receive a tag that holds a web link?

Through Android App Links, which the documentation points to from Android 16. An NDEF filter on the https address no longer receives the tag, and from Android 17 the link waits for the user to act on a notification.