Skip to main content
Configure this only to handle a tag tapped while your app is closed or in the background. Reader mode — nfc.withTag, nfc.openSession, nfc.onTag — needs none of it, and adding it unnecessarily means unrelated tags start launching your app.

Reading the tag

Two entry points, because a tag that started the app and a tag that arrived while it was running are genuinely different situations.
Both hand the tag to a callback and release it afterwards, the way withTag does. A native handle that outlives its scope is a leak whose failure surfaces somewhere else entirely, so there is no version of these that hands one out and trusts you to give it back.
readNdef() works; the radio usually does not. By the time any JavaScript runs, the card has almost always left the field — the user tapped and pocketed it. What survives is the message the system read before dispatching the intent, which travelled with it, so reading NDEF still answers.Anything needing the radio (transceive, writeNdef, getNdefStatus) fails with tagLost unless the card genuinely is still there.
Two more behaviours worth knowing:
  • withLaunchTag consumes the tag: a second call answers null. That is also what stops a screen rotation from replaying a tap from minutes ago, since the recreated activity is handed the same launch intent.
  • tag.onLost never fires for a background tag. It is not being watched, because a watcher would do nothing but announce a departure that happened before the app was looking.

Expo

app.json

NDEF filters

Each entry becomes one intent filter, matched against the first record of the tag’s message. Filter on a MIME type you control, or on your own URI scheme. They are kept as separate filters rather than merged, because Android combines sibling <data> attributes combinatorially: one filter holding both scheme="https" and mimeType="text/plain" matches any https URI or any text/plain payload, which is not what the config appears to say.
One caveat that catches people out: from Android 16, an NDEF tag holding an http or https URI dispatches ACTION_VIEW instead of ACTION_NDEF_DISCOVERED, so a scheme filter for those two no longer fires on newer devices. Your own scheme and MIME-type filters are unaffected.

Tech lists

Each inner array is one <tech-list>, and a tag matches when it supports every technology in that list. Separate lists are alternatives. So the example above matches an ISO-DEP tag, or a tag that is both MIFARE Ultralight and NDEF. ACTION_TAG_DISCOVERED is never written. It is deprecated as of API 37, and it is also the widest filter there is — it fires for any tag at all, including ones your app has no idea what to do with.

iOS

There is nothing to configure. iOS reads NDEF tags in the background on its own, without the app being involved, and opens a URL record’s link. It cannot be extended to other technologies and cannot be turned off by an app.

The Android 17 permission

From Android 17 (API 37), an activity receiving NFC intents must be protected by android.permission.DISPATCH_NFC_MESSAGE when the app targets an SDK above BAKLAVA, so that only the NFC system service can dispatch to it. The trap is that android:permission on an activity applies on every Android version, and a permission the running platform does not define can be held by nobody — so applying it on a device older than API 37 stands to block the very dispatch it is meant to secure. One manifest ships to every version, so this is a real choice, not a formality. dispatchNfcMessagePermission therefore defaults to 'auto', which is narrow on purpose: To let 'auto' decide, declare the target SDK explicitly:
app.json
This is provisional. The behaviour above follows from the Android 17 documentation and has not been validated on API 37 hardware. If you are testing on a device that new, dispatchNfcMessagePermission lets you decide it outright, and a report either way is welcome.

Bare React Native

android/app/src/main/AndroidManifest.xml

android/app/src/main/res/xml/nfc_kit_tech_filter.xml

The file name is arbitrary in a bare project — what matters is that the <meta-data> above points at it — but keeping this name means a later prebuild would overwrite the same file rather than leaving two.

iOS

Unchanged from the default setup: background reading is an Android-only configuration.

Checking it works

Then tap a matching tag with the app closed. If nothing happens, in order of likelihood:
1

The first record does not match the filter

Only the first record of the message is matched against an NDEF filter.
2

The activity is missing singleTop

Android recreates it on every intent otherwise.
3

You are on API 37 with the permission set the wrong way

See the section above; dispatchNfcMessagePermission decides it outright.