Python NFC Programming Guide: nfcpy Library
nfcpy by Stephen Tiedemann is the most mature Python library for NFC readerNFC readerActive device generating RF field to initiate communication with tagsView full → hardware, supporting contactless cards, tag operations, and peer-to-peer sessions on Linux and macOS. It communicates with USB, SPI, and UART-connected NFC reader ICs including the PN532, PN533, RCS380, and ACR122U.
Installation
pip install nfcpy
# Optionally for USB device detection:
pip install libusb1 pyserial
On Linux, grant your user permission to access USB without sudo:
sudo adduser $USER plugdev
# Create udev rule for ACR122U or similar
echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="072f", MODE="0666", GROUP="plugdev"' \
| sudo tee /etc/udev/rules.d/93-nfc.rules
sudo udevadm control --reload-rules
Connecting to a Reader
import nfc
def connected(tag):
print(f"Tag found: {tag}")
return True # return False to stop after one tag
with nfc.ContactlessFrontend('usb') as clf:
clf.connect(rdwr={'on-connect': connected})
nfc.ContactlessFrontend accepts:
- 'usb' — auto-detect first USB NFC reader
- 'usb:072f:2200' — specific vendor/product IDs
- 'tty:S0:pn532' — serial UART device
- 'i2c:0' — I2C bus (Raspberry Pi)
The PN532 over I2C is the most common setup for Raspberry Pi projects. See NFC Arduino and Raspberry Pi for wiring.
Reading NDEF from a Tag
import nfc
import ndef
def on_connect(tag):
if tag.ndef is None:
print("Tag has no NDEF content")
return False
print(f"NDEF version: {tag.ndef.version}")
print(f"Capacity: {tag.ndef.capacity} bytes")
print(f"Writeable: {tag.ndef.is_writeable}")
for record in tag.ndef.records:
if isinstance(record, ndef.UriRecord):
print(f"URI: {record.iri}")
elif isinstance(record, ndef.TextRecord):
print(f"Text ({record.language}): {record.text}")
elif isinstance(record, ndef.SmartposterRecord):
print(f"SmartPoster URI: {record.resource}")
else:
print(f"Record type: {record.type!r}")
return True
with nfc.ContactlessFrontend('usb') as clf:
clf.connect(rdwr={'on-connect': on_connect})
The tag.ndef attribute is None if the tag has no NDEF capability container or is not NDEF-formatted. All record types map to Python objects via the companion ndeflib package (pip install ndeflib).
Writing NDEF to a Tag
import nfc
import ndef
def write_url(tag):
if tag.ndef is None:
print("Cannot write: no NDEF support")
return False
if not tag.ndef.is_writeable:
print("Tag is write-protected — check [lock-bits](/glossary/lock-bits/)")
return False
records = [ndef.UriRecord("https://nfcfyi.com")]
tag.ndef.records = records
print(f"Written {len(records)} record(s)")
return False # stop after write
with nfc.ContactlessFrontend('usb') as clf:
clf.connect(rdwr={'on-connect': write_url})
Low-Level Tag Access
For operations beyond NDEF — reading raw memory blocks, setting password-protection, or writing otp bytes — access the tag's underlying type object:
def raw_access(tag):
# Type 2 tag (e.g. NTAG213/215/216)
if isinstance(tag, nfc.tag.tt2.Type2Tag):
# Read 4 bytes at page 4 (user memory starts at page 4)
data = tag.read(4)
print(f"Page 4: {data.hex()}")
# Write 4 bytes to page 4
tag.write(4, bytearray([0x01, 0x02, 0x03, 0x04]))
# Type 4 tag (e.g. DESFire)
elif isinstance(tag, nfc.tag.tt4.Type4Tag):
response = tag.send_apdu(0x00, 0xA4, 0x04, 0x00)
print(f"APDU response: {response.hex()}")
return False
NTAG Password Protection
def set_password(tag):
if not isinstance(tag, nfc.tag.tt2_nxp.NTAG215):
return False
# Write 4-byte password to page 85 (0x55)
tag.write(0x55, bytearray([0x41, 0x42, 0x43, 0x44]))
# Write 2-byte PACK to page 86
tag.write(0x56, bytearray([0xAA, 0xBB, 0x00, 0x00]))
# Set AUTH0 in CFG0 (page 83): protect from page 4 onwards
cfg0 = bytearray(tag.read(0x53))
cfg0[3] = 0x04 # AUTH0 = page 4
tag.write(0x53, cfg0)
print("Password set")
return False
Always test password configurations with a test tag before deploying. Incorrect access-control-bits can permanently lock a tag.
Card Emulation and Peer-to-Peer
nfcpy supports card-emulation-mode on readers with hardware support (e.g. RC-S380):
def on_startup(target):
target.sensf_res = bytearray.fromhex("0112FC..." )
return target
def on_connect(llc):
# LLCP logical link control
print("P2P connected")
return True
with nfc.ContactlessFrontend('usb') as clf:
clf.connect(llcp={'on-startup': on_startup, 'on-connect': on_connect})
llcp and snep are used for peer-to-peer data exchange and legacy Android Beam compatibility.
Batch Tag Processing Script
import nfc, ndef, time
tags_processed = 0
def process_tag(tag):
global tags_processed
if tag.ndef:
for record in tag.ndef.records:
if isinstance(record, ndef.UriRecord):
print(f"[{tags_processed:04d}] {record.iri}")
tags_processed += 1
return True # keep looping
with nfc.ContactlessFrontend('usb') as clf:
print("Place tags on reader. Ctrl+C to stop.")
try:
clf.connect(rdwr={'on-connect': process_tag, 'interval': 0.1})
except KeyboardInterrupt:
print(f"Processed {tags_processed} tags")
The interval parameter (seconds) controls how quickly the reader polls for new tags after the previous one is removed. Use 0.1 s for rapid batch processing.
Supported Reader Hardware
| Reader | Interface | Notes |
|---|---|---|
| ACR122U | USB | Most common; PN532 inside |
| Identiv uTrust 3700F | USB | ISO 15693ISO 15693Standard for vicinity-range smart cards, 1+ meter read rangeView full → + 14443 |
| Sony RC-S380 | USB | P2P / HCE capable |
| PN532 breakout | SPI / I2C / UART | Raspberry Pi / Arduino |
| SCL3711 | USB | CCID compliant |
Use the NFC Reader Modules Compared guide for IC-level selection.
See also: NFC Arduino and Raspberry Pi | ESP32 NFC Development | NDEF Specification Deep Dive | NFC Tag Types Explained