18 NFC Tag Programming
18.1 Start With the Story
Programming a tag is not done when the writer reports success. It is done when the record plan fits the tag, the bytes are written, the data is read back, locks or passwords are understood, and the release note says what should happen if a scan fails.
Use this chapter as a write-verify story. Plan the NDEF payload, choose the tool path, test the result on the intended reader, and leave evidence that the tag can be trusted only for the job it was designed to do.
18.2 Learning Objectives
By the end of this chapter, you should be able to:
- Plan URI, Text, MIME, and external-type NDEF records for an IoT tag without storing secrets in public payloads.
- Check tag capacity, write state, and platform support before attempting a write.
- Implement safe write patterns for Android, Python/nfcpy, and PN532-based embedded readers.
- Verify a written tag by reading it back and validating the payload, type, length, and application policy.
- Diagnose common write failures caused by tag lock state, memory limits, interface wiring, antenna coupling, and mounting material.
18.3 Quick Check: NFC Tag Programming
18.4 Minimum Viable Understanding
- Programming is not just writing bytes. A safe tag write includes record planning, capacity checks, write access checks, write, read-back verification, and a release record.
- NDEF is public unless another layer protects it. Do not store passwords, access tokens, private keys, or one-time codes as ordinary NDEF text.
- UIDs and NDEF records have different jobs. A UID can identify a tag, while an NDEF record can identify content or launch an app. Neither one proves authorization by itself.
- Tag memory is tight. Small Type 2 tags work well for one URL or compact app link; larger or protected records need a tag with explicit headroom.
- The physical install matters. Metal surfaces, wet areas, phone cases, and antenna position can turn a clean desk test into a field failure.
18.5 Programming Workflow
Start with a write record, not with code. The same workflow applies whether the writer is an Android phone, a USB reader, a Raspberry Pi, or a microcontroller with a PN532 module.
18.6 Programming Is Records Plus Lifecycle
Writing an NFC tag has two halves that are easy to blur. First, compose the NDEF message: choose which record types encode the behavior you want. Then decide the memory lifecycle: whether the tag stays rewritable, is password-protected, or is permanently frozen read-only. A poster sticker anyone could overwrite and a tamper-resistant asset tag may use the same records but opposite lifecycle choices.
Think about a gateway maintenance rollout before writing code. The team needs 80 labels. Each tap should open one maintenance record, show a short human label, and let the app request authenticated service actions. The tag should not contain a technician password, service command, or bearer token. A safe record plan is a URI such as https://ops.example.invalid/a/gateway-041 plus a Text record such as Gateway 041 maintenance record. The tag selects context; the app and backend decide whether the current technician may do anything privileged.
That split gives you a release checklist. Before writing, estimate encoded NDEF size and compare it with the tag’s usable capacity, leaving room for a future path or version field. During writing, reject tags that are read-only, already protected, too small, or not NDEF formatted. After writing, read the tag back with the same class of phone or reader used in the field, then compare record count, type, order, host, asset identifier, and length. Only then choose the lifecycle control: leave lab tags rewritable, password-protect a staff-maintained batch, or permanently lock public poster tags whose URL must never change.
The most common design error is treating the write command’s success flag as the release event. It is only one command result. The release event is evidence: written payload, decoded read-back, protection state, tag batch, app version, physical surface, and replacement procedure. That evidence is what lets a maintenance team debug a future failure without guessing whether the problem is RF coupling, a bad NDEF record, an app validation rule, or a tag that was locked too early.
18.7 NDEF Record Planning
NDEF messages can contain multiple records, but a good programming workflow keeps the record set small and predictable.
18.8 Record Types and Validation Rules
The record type determines both the platform behavior and the validation rule. A message can hold several records, and order matters: the first record usually drives the primary action while later records act as fallbacks or hints. A “tap to open the app, or the web page if the app is missing” tag is one multi-record message, not two unrelated tags.
| Record | What it encodes | Detail that matters |
|---|---|---|
| URI | A link or scheme such as HTTPS, tel, or mailto | Prefix bytes can abbreviate common URI starts, so measure the encoded message rather than guessing from visible characters. |
| Text | Human-readable label, instruction, or fallback note | Carries a language code and UTF-8 or UTF-16 flag; keep it short and non-sensitive. |
| Smart Poster | A URI bundled with title, action, and optional icon | Useful when the record should say both what to show and what action to prefer. |
| Android Application Record | Android package name stored as an external type | Can guide app launch or install behavior; provide a web fallback when the app is absent. |
| Handover | Wi-Fi or Bluetooth setup material | Bootstraps a faster radio and belongs in a flow with its own credential and rotation policy. |
A practical maintenance tag might put the stable HTTPS URI first, a short Text label second, and an app-specific MIME or external-type record only when the app owns a documented schema. The validation rule should say what happens when records arrive in the wrong order, when an unknown MIME type appears, or when the URI host is not the approved host.
For a batch station, make the checks mechanical. Scan a blank tag, log its type and writable capacity, encode the planned records, and compare the encoded byte count with capacity before writing. A 40-character URL and a 30-character label usually fit a small Type 2 tag, but app-specific MIME records, icons, long JSON, and future version fields can push a design into a larger tag. Decide from the encoded message that the writer library will actually send, because URI abbreviation bytes, language codes, record headers, and text encoding all affect the result.
18.8.1 Capacity Rule
Before writing, estimate the encoded size and keep headroom for future revision. A simple URI tag can fit on a small Type 2 tag. A multi-record commissioning tag may need a larger Type 2, Type 4, or Type 5 tag depending on the app and reader requirements.
18.9 Android Write Pattern
Android NFC writing should happen in a foreground workflow where the user intentionally taps a tag while the app is ready to write. The app should check whether the tag already supports NDEF, whether it is writable, whether the message fits, and whether a newly formatted tag is needed.
NdefMessage buildMessage(String assetUrl, String label) {
NdefRecord uri = NdefRecord.createUri(assetUrl);
NdefRecord text = NdefRecord.createTextRecord("en", label);
return new NdefMessage(new NdefRecord[] { uri, text });
}
boolean writeMessage(Tag tag, NdefMessage message) throws Exception {
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
ndef.connect();
try {
if (!ndef.isWritable()) return false;
if (message.toByteArray().length > ndef.getMaxSize()) return false;
ndef.writeNdefMessage(message);
return true;
} finally {
ndef.close();
}
}
NdefFormatable formatable = NdefFormatable.get(tag);
if (formatable == null) return false;
formatable.connect();
try {
formatable.format(message);
return true;
} finally {
formatable.close();
}
}- Use an explicit write state in the UI so the user knows when to hold the tag steady.
- Handle
TagLostExceptionand repeat the write only after the user taps again. - Validate the read-back message before showing success.
- Avoid writing credentials or secrets to NDEF records. Use the tag as a reference to authenticated app or backend policy.
18.10 Python and USB Reader Pattern
Python tools are useful for programming batches, lab stations, and maintenance tags. The writer should read the tag first, reject non-writable or undersized tags, write the records, and then read the records back.
import nfc
import ndef
RECORDS = [
ndef.UriRecord("https://ops.example.invalid/a/gateway-041"),
ndef.TextRecord("Gateway 041 maintenance record", language="en"),
]
def on_tag(tag):
if not tag.ndef:
print("Tag is not NDEF formatted")
return False
if not tag.ndef.is_writeable:
print("Tag is read-only")
return False
encoded = b"".join(ndef.message_encoder(RECORDS))
if len(encoded) > tag.ndef.capacity:
print("Payload does not fit this tag")
return False
tag.ndef.records = RECORDS
read_back = list(tag.ndef.records)
if [r.type for r in read_back] != [r.type for r in RECORDS]:
print("Read-back validation failed")
return False
print("Tag written and verified")
return False
with nfc.ContactlessFrontend("usb") as reader:
reader.connect(rdwr={"on-connect": on_tag})nfc.ContactlessFrontend("usb") is common for supported USB readers. PN532 boards may require a serial or driver-specific connection string. Keep the exact station hardware and driver version in the deployment notes so batch writes are repeatable.
18.11 PN532 Embedded Reader Pattern
An embedded reader is usually better for reading and validating tags in a product flow than for mass programming. If it writes tags, keep the write flow supervised and include verification.
#include <Wire.h>
#include <PN532_I2C.h>
#include <PN532.h>
#include <NfcAdapter.h>
PN532_I2C pn532_i2c(Wire);
NfcAdapter nfc(pn532_i2c);
void setup() {
Serial.begin(115200);
nfc.begin();
Serial.println("Hold a writable NDEF tag near the reader");
}
void loop() {
if (!nfc.tagPresent()) {
delay(250);
return;
}
NfcTag tag = nfc.read();
Serial.print("UID: ");
Serial.println(tag.getUidString());
if (!tag.hasNdefMessage()) {
Serial.println("No NDEF message found");
return;
}
NdefMessage message = tag.getNdefMessage();
Serial.print("Records: ");
Serial.println(message.getRecordCount());
}- Set the PN532 board to the interface used by the sketch: I2C, SPI, or UART.
- Confirm voltage compatibility for the specific breakout board before connecting it to a microcontroller.
- Keep SDA/SCL, SPI chip select, or UART pins documented in the lab record.
- Test one known-good formatted tag before debugging application code.
18.12 Write and Verify Loop
Write failures often look like user mistakes, but the root cause is usually a missing preflight check or a missing read-back step.
18.13 Folded Implementation Record
Implementation starts before the first write. Keep a short record beside each tag workflow:
| Record field | What it proves |
|---|---|
| Intended action | The tag selects an object or workflow; privileged action still requires app policy. |
| NDEF schema | Record type, order, version, host, length limit, encoding, and unsupported-record behavior are known before writing. |
| Platform path | Android app, Python station, PN532 reader, or production fixture has an owner and test version. |
| Write result | The tag was written, read back, decoded, compared with the expected record, and rejected on mismatch. |
| Release evidence | Tag type, capacity, lock state, reader station, app version, final mounting test, and fallback are recorded. |
For gateway replacement tags, fail closed on unknown host, unexpected record type, oversized payload, read-only tag, or missing asset ID. Do not store technician credentials, service commands, or private tokens in public NDEF records; use the tap to select context, then let the authenticated application authorize the action.
18.14 Tag Selection for Programming
Select tags by payload, write lifecycle, phone compatibility, environment, and security need.
18.15 Lock Bits and Password Protection on NTAG
Take the common NTAG21x family as the concrete example. NTAG213, NTAG215, and NTAG216 provide roughly 144, 504, and 888 bytes of user memory, respectively. Their memory model gives you two different write controls, and confusing them creates deployment bugs.
- Lock bytes set individual pages to read-only. Static and dynamic lock bits are one-way; once a page is locked, it cannot be rewritten. This is how you permanently freeze a public poster tag so visitors cannot swap its URL.
- Password protection uses a 32-bit PWD and a 16-bit PACK acknowledgement to gate writes, or sometimes reads, behind a shared secret. An authorized writer can still update the tag, so this protects an update process rather than freezing content.
“Make this tag safe” therefore has two different answers. Lock it if the content is final and must never change. Password-protect it if it must stay updatable by staff but not by the public. NTAG21x also exposes a one-way NFC counter and a Capability Container that a reader checks first to learn the memory size and whether the tag is already read-only. The Capability Container is why a careful writer inspects the tag before writing instead of blasting records at every UID it sees.
The release order matters. First inspect tag type, capacity, and write state. Then write the NDEF TLV containing the planned records. Then read back the decoded message and compare it with the plan. Only after that should you set irreversible lock bits or configure the password boundary. If a public poster tag is locked before read-back and the URL has a typo, the mistake becomes permanent.
The security boundary is narrower than many product sketches imply. A password gate on a low-cost tag is a write-control feature, not a substitute for backend authorization, and an NFC UID or public NDEF value can be copied in many practical deployments. When cloning matters, use a tag family and application protocol designed for cryptographic proof, or treat the NFC tap as a lookup key validated by an authenticated app session. Match the memory control to the consequence: lock immutable public content, password-protect supervised update batches, and move authorization out of ordinary NDEF payloads.
18.16 Check Lock Versus Password Protection
18.17 Debugging Map
When a tag does not work, isolate the failure layer before changing code.
- Tag not detected. Check reader interface mode, wiring, power, antenna distance, tag orientation, and whether the tag is supported by the reader.
- Write fails. Check read-only state, password/write protection, message size, tag formatting, and whether the user moved the tag during the write.
- Phone opens the wrong app. Check URI scheme, app-link association, MIME type, record order, and whether the OS supports the intended record behavior.
- Works on the bench but not installed. Retest with the final surface, enclosure, phone case, label overlay, and user tap location.
- Security review rejects the design. Remove secrets from NDEF, move authorization to the app/backend, and add cryptographic proof where cloning matters.
18.18 Worked Example: Gateway Maintenance Tag Batch
A plant maintenance team needs to program NFC labels for 80 gateways. A tap should open the gateway maintenance record, show a short local label, and let the app request authenticated service actions. The labels will be mounted on metal enclosures.
18.18.1 Record Plan
18.18.2 Programming Station Checklist
18.18.3 Acceptance Criteria
- Every tag decodes to the expected host and asset ID.
- The tag does not contain technician credentials, service commands, or private tokens.
- The app refuses unknown hosts, unexpected record types, and oversized payloads.
- The final installation passes tap tests with the target phone models and protective cases.
- A printed asset ID or manual search path exists when NFC fails.
18.19 Knowledge Check
18.20 Quick Check: Safe NFC Write Workflow
18.21 Matching Quiz: Programming Decisions
18.22 Ordering Quiz: Program and Release a Tag
18.23 Summary
- NFC tag programming is a release workflow: plan, inspect, write, read back, protect, and document.
- Android, Python, and PN532-based readers can all support tag work, but each needs explicit capacity, write-state, and error handling.
- NDEF records are useful for interoperability but should be treated as untrusted public input.
- Tag selection depends on payload size, write lifecycle, environment, phone support, and security needs.
- Production tags need field evidence from the final mounting surface and target reader devices.
18.24 Key Takeaway
Programming NFC tags requires more than writing bytes: choose the tag, NDEF structure, lock policy, validation path, and update workflow together.