What is an NDEF message, and how is it built?
An NDEF message is an ordered sequence of records. Each record carries a short header describing what kind of thing its payload is, followed by the payload itself. That header is the entire point: it lets a reader that has never seen the tag decide what to do with what it finds.
A message is a list of records
One tag holds one NDEF message. That message contains one or more records, in a defined order, and flags mark which record is first and which is last so a reader knows where the message begins and ends.
Most tags in the wild hold exactly one record. Several are possible and cost both space and read time, which is a trade worth making deliberately rather than by habit.
What a record contains
Android documents the structure precisely in its NdefRecord reference, which is the clearest freely available description.
A record always carries a three bit type name field, usually written TNF, which gives high level typing for the rest of the record. Beyond that come three variable length fields: a type giving detailed typing for the payload, an optional identifier that is rarely used in practice, and the payload itself.
| Field | Always present | What it does |
|---|---|---|
| TNF | Yes, three bits | High level typing, tells the reader how to interpret the type field |
| Type | Variable length | Detailed typing, such as a URI scheme or a MIME type |
| Id | Optional | Identifier metadata, not commonly used |
| Payload | Variable length | The actual content |
Why the header matters more than the payload
Two tags can hold identical bytes of payload and behave completely differently, because one declares them a web address and the other declares them plain text.
The first opens a browser. The second is displayed, or ignored entirely, depending on the platform. Nothing about the bytes changed, and everything about the outcome did.
This is the single most useful thing to understand about NDEF, and it explains most cases of a tag that reads correctly and does nothing. The content was fine. The declaration was wrong.
What you see in a tool
A reading tool typically shows the record count, then for each record its type, its size and its content. If a record shows a size but no recognisable content, the payload is there and the type is something the tool does not decode.
An empty message is a valid state and not a fault. A formatted tag with nothing written holds a message with a single empty record, which is why a fresh tag reads successfully and does nothing.
Frequently asked questions
Can a tag hold more than one NDEF message?
One message per tag, containing as many records as fit. The message is the container and the records are its contents, so the question of several messages does not arise in practice.
Does record order matter?
Yes, and more than people expect, since platforms generally act on the first record they recognise. Putting a fallback before the record you actually want triggered is a common and avoidable mistake.
Is an empty tag the same as an unformatted tag?
No. An unformatted tag has no structure a generic reader can use. An empty formatted tag has the structure and no content, and the difference matters because only the second is ready to be written normally.
Do I need to understand this to write a tag?
Not to write a URL with a tool, which handles it for you. It becomes necessary the moment something behaves unexpectedly, because almost every surprise traces back to a record type rather than to content.