How to Read NFC Tag NDEF Data (2026)

NFC tags are everywhere — in business cards, smart posters, product packaging, access control cards, and home automation triggers. But how do you actually read the data stored on an NFC tag programmatically? This guide covers the NDEF data format, Android NFC API code, Python examples, tag type detection, and debugging tips.

Try it now: CardWise NDEF Parser — paste hex NDEF data and decode Text, URI, Smart Poster, and MIME records in your browser.

What is NDEF?

NDEF (NFC Data Exchange Format) is the standard data format defined by the NFC Forum for storing application data on NFC tags. NDEF provides a lightweight, binary container format that any NFC device can read, regardless of the underlying tag hardware (NTAG, MIFARE, FeliCa, Topaz).

An NDEF message is a sequence of one or more NDEF records. Each record carries a typed payload (text string, URL, MIME-type data, or custom application data). The first record is the header record, and the last record has the "ME" (Message End) flag set.

NDEF Record Structure

Every NDEF record starts with a 3-byte header:

Byte 0: Flags
  bit 7: MB (Message Begin)
  bit 6: ME (Message End)
  bit 5: CF (Chunk Flag)
  bit 4: SR (Short Record)
  bit 3: IL (ID Length present)
  bits 2-0: TNF (Type Name Format)

Byte 1: Type Length (number of bytes in Type field)
Byte 2: Payload Length (1 byte if SR=1, 4 bytes if SR=0)

[Optional] ID Length (if IL=1)
[Optional] Type field (Type Length bytes)
[Optional] ID field (ID Length bytes)
Payload (Payload Length bytes)

NDEF Record Types (TNF)

The TNF (Type Name Format) field in the record header tells you what kind of data is in the payload:

TNFValueType FieldCommon Use
NFC Forum Well-Known Type0x01"T" (Text), "U" (URI)Plain text, URLs
MIME media-type0x02e.g., "text/plain", "image/png"vCard, images, custom MIME
Absolute URI0x03Full URI stringCustom app deep links
NFC Forum External Type0x04e.g., "album:example.com"Custom app-specific data
Unknown0x05(empty)Opaque binary data
Unchanged (chunked)0x06(empty)Continuation of a chunked record
Empty0x00(empty)Empty record (no payload)

Reading NDEF on Android (Kotlin)

// 1. Register for NDEF discovery in AndroidManifest.xml:
// <intent-filter>
//   <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
//   <data android:scheme="https" android:host="example.com"/>
// </intent-filter>

// 2. Handle the NDEF intent in your Activity:
override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    if (intent.action == NfcAdapter.ACTION_NDEF_DISCOVERED) {
        val rawMessages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)
        rawMessages?.forEach { parcelable ->
            val ndefMessage = parcelable as NdefMessage
            ndefMessage.records.forEach { record ->
                when (record.tnf) {
                    NdefRecord.TNF_WELL_KNOWN -> {
                        if (record.types contentEquals NdefRecord.RTD_TEXT) {
                            val text = parseTextRecord(record)
                            Log.d("NFC", "Text: $text")
                        } else if (record.types contentEquals NdefRecord.RTD_URI) {
                            val uri = record.toUri()
                            Log.d("NFC", "URI: $uri")
                        }
                    }
                    NdefRecord.TNF_MIME_MEDIA -> {
                        val mimeType = String(record.type)
                        Log.d("NFC", "MIME: $mimeType, size=${record.payload.size}")
                    }
                }
            }
        }
    }
}

fun parseTextRecord(record: NdefRecord): String {
    val payload = record.payload
    val status = payload[0].toInt()
    val languageCodeLength = status and 0x3F
    val encoding = if ((status and 0x80) == 0) Charsets.UTF_8 else Charsets.UTF_16
    return String(payload, 1 + languageCodeLength,
                   payload.size - 1 - languageCodeLength, encoding)
}

Reading NDEF with Python (pyscard + ACR122U)

from smartcard.System import readers
from smartcard.util import toHexString, toBytes

# Connect to ACR122U reader
reader = readers()[0]
connection = reader.createConnection()
connection.connect()

# Select NDEF application (Type 4 Tag)
SELECT_NDEF = [0x00, 0xA4, 0x04, 0x00, 0x07,
               0xD2, 0x76, 0x00, 0x00, 0x85, 0x01, 0x01, 0x00]
_, sw1, sw2 = connection.transmit(SELECT_NDEF)
assert (sw1, sw2) == (0x90, 0x00), f"Select failed: {sw1:02X}{sw2:02X}"

# Select NDEF Capability Container (file 0xE103)
SELECT_CC = [0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x03]
_, sw1, sw2 = connection.transmit(SELECT_CC)
assert (sw1, sw2) == (0x90, 0x00)

# Read CC to find NDEF file size
READ_BINARY = [0x00, 0xB0, 0x00, 0x00, 0x0F]
data, sw1, sw2 = connection.transmit(READ_BINARY)
ndef_file_size = (data[7] << 8) | data[8]

# Select NDEF file (0xE104)
SELECT_NDEF_FILE = [0x00, 0xA4, 0x00, 0x0C, 0x02, 0xE1, 0x04]
_, sw1, sw2 = connection.transmit(SELECT_NDEF_FILE)

# Read NDEF message
READ_NDEF = [0x00, 0xB0, 0x00, 0x00, 0x02]  # First 2 bytes: NDEF length
ndef_len_data, _, _ = connection.transmit(READ_NDEF)
ndef_len = (ndef_len_data[0] << 8) | ndef_len_data[1]

READ_FULL = [0x00, 0xB0, 0x00, 0x00, min(ndef_len, 0xFF)]
ndef_data, _, _ = connection.transmit(READ_FULL)

# NDEF data starts at byte 2 (after length field)
print(f"NDEF data ({ndef_len} bytes): {toHexString(ndef_data[2:])}")
Debug tip: Paste the hex NDEF data into the CardWise NDEF Parser to see the decoded records, types, and payloads in a structured tree view — perfect for verifying your code output.

Tag Type Detection

Different NFC tag types (Type 1-5) store NDEF data differently. You need to know the tag type to read NDEF correctly:

Tag TypeStandardCommon ChipsNDEF Storage
Type 1ISO 14443-3ATopaz, InnovisionTLV in memory blocks
Type 2ISO 14443-3ANTAG213/215/216, MIFARE UltralightTLV (0x03) in user memory
Type 3JIS X 6319-4FeliCaService-based read
Type 4ISO 14443-4MIFARE DESFire, SmartMXNDEF file (0xE104) in application
Type 5ISO 15693ICODE SLI, Tag-itTLV in block memory

For Type 2 tags (most common for NTAG), NDEF data is stored as a TLV block in the user memory area, starting at the 0x03 tag byte. For Type 4 tags (DESFire), NDEF is stored in a dedicated file (FID 0xE104) within the NDEF application (AID D2760000850101).

Debugging Tips

Frequently Asked Questions

How do I read an NFC tag with my phone?

On Android: enable NFC in Settings, then tap the phone against the tag. If the tag has a URI record, the phone auto-opens the URL. For NDEF inspection, use an NFC tool app (NFC Tools, Trigger). On iPhone (XS and later): Background Tag Reading works automatically — just tap the tag while the screen is on. Third-party apps can read NDEF via CoreNFC framework.

What is the difference between NDEF and raw tag data?

NDEF is a high-level application data format. Raw tag data includes hardware-level data (UID, ATQA, SAK, ATS) and memory blocks. NDEF sits inside the tag's user memory in a TLV (Type-Length-Value) structure. To read NDEF, you locate the NDEF Message TLV (tag byte 0x03) in the tag memory, read the length, then parse the NDEF message bytes.

Can all NFC tags store NDEF data?

Most NFC Forum-compliant tags can, but MIFARE Classic tags are not fully NFC Forum compliant — they store NDEF in a proprietary format that requires authentication keys. NTAG213/215/216, MIFARE Ultralight, and MIFARE DESFire all support standard NDEF. Use the NFC Tag Type Detector to identify your tag type from ATR/ATQA/SAK values.

How much NDEF data can I store on a tag?

It depends on the tag chip's user memory. NTAG213: 144 bytes (usable ~130 bytes NDEF). NTAG215: 504 bytes (Amiibo compatible). NTAG216: 888 bytes (vCard contacts). MIFARE DESFire EV2: up to 4KB per file. Use the NFC Tag Capacity Calculator to calculate exact NDEF capacity including overhead.