Python NFC Programming Guide

nfcpy Library for Desktop Applications

| 4 min read

Python NFC Programming Guide: nfcpy Library

nfcpy by Stephen Tiedemann is the most mature Python library for NFC reader 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 15693 + 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

Terms in This Guide