Disclosure: As an Amazon Associate, CardWise earns from qualifying purchases at no additional cost to you. This does not affect our recommendations.

How to Write NDEF to NFC Tags — Complete Encoding & Writing Guide

Writing NDEF data to an NFC tag involves two steps: encoding your data as NDEF binary records, then writing those bytes to the tag using platform-specific APIs. This guide covers everything: building NDEF record headers, encoding Text/URI/Smart Poster/WiFi/Bluetooth records, and writing to tags via Android, iOS, Web NFC, and PC/SC readers.

1. Encoding a Single NDEF Record to Binary

def encode_ndef_record(tnf, record_type, payload, id=None):
    """Build one NDEF record as bytes."""
    flags = tnf & 0x07  # TNF in lower 3 bits
    flags |= 0x80       # MB = 1
    flags |= 0x40       # ME = 1

    sr = len(payload) < 256
    if sr:
        flags |= 0x10   # SR = 1, 1-byte payload length
    if id is not None:
        flags |= 0x04   # IL = 1

    header = bytes([flags, len(record_type)])
    if sr:
        header += bytes([len(payload)])
    else:
        header += bytes([0x00, 0x00, (len(payload) >> 8) & 0xFF, len(payload) & 0xFF])
    if id is not None:
        header += bytes([len(id)])
    header += record_type.encode() if isinstance(record_type, str) else record_type
    if id is not None:
        header += id
    header += payload
    return header

2. Encoding Common NDEF Record Types

Text Record ("T")

def encode_text_record(text, lang='en', utf16=False):
    """Encode NDEF Text Record."""
    status = (0x80 if utf16 else 0x00) | (len(lang) & 0x3F)
    encoding = 'utf-16-be' if utf16 else 'utf-8'
    payload = bytes([status]) + lang.encode('ascii') + text.encode(encoding)
    return encode_ndef_record(0x01, b'T', payload)  # TNF=Well-Known, type="T"

URI Record ("U")

URI_PREFIXES = {
    'http://www.': 0x01, 'https://www.': 0x02,
    'http://': 0x03, 'https://': 0x04,
    'tel:': 0x05, 'mailto:': 0x06,
}

def encode_uri_record(uri):
    """Encode NDEF URI Record with abbreviation prefix."""
    for prefix, code in URI_PREFIXES.items():
        if uri.startswith(prefix):
            payload = bytes([code]) + uri[len(prefix):].encode('utf-8')
            return encode_ndef_record(0x01, b'U', payload)
    # No prefix match: use code 0x00 (no abbreviation)
    return encode_ndef_record(0x01, b'U', bytes([0x00]) + uri.encode('utf-8'))

# encode_uri_record('https://cupass.com') → prefix=0x04 + 'cupass.com'

WiFi Credential Record ("application/vnd.wfa.wsc")

def encode_wifi_record(ssid, password, auth_type='WPA2-Personal'):
    """Encode NDEF WiFi Peer-to-Peer record."""
    # Build WSC (WiFi Simple Configuration) TLV
    # This is a MIME record with type "application/vnd.wfa.wsc"
    payload = bytearray()
    # Credential TLV (0x100E)
    cred = bytearray()
    cred += bytes([0x10, 0x45, 0x00, len(ssid)]) + ssid.encode()  # SSID
    cred += bytes([0x10, 0x27, 0x00, len(password)]) + password.encode()  # Network Key
    cred += bytes([0x10, 0x03, 0x00, 0x02])  # Auth Type = WPA2-Personal
    payload = cred
    return encode_ndef_record(0x02, b'application/vnd.wfa.wsc', bytes(payload))

3. Writing NDEF — Android (Java/Kotlin)

// Android: Write NDEF using Ndef or NdefFormatable
Ndef ndef = Ndef.get(tag);
if (ndef != null) {
    try {
        ndef.connect();
        if (!ndef.isWritable()) {
            // Tag is read-only
            return;
        }
        // Check capacity
        NdefMessage msg = ndef.getCachedNdefMessage();
        if (msg != null && ndef.getMaxSize() < msg.toByteArray().length) {
            // NDEF too large
            return;
        }
        ndef.writeNdefMessage(ndefMessage);
        ndef.close();
    } catch (FormatException e) {
        // Tag not NDEF formatted — use NdefFormatable
        NdefFormatable formatable = NdefFormatable.get(tag);
        if (formatable != null) {
            formatable.connect();
            formatable.format(ndefMessage);
            formatable.close();
        }
    }
}

4. Writing NDEF — Web NFC API

// Web NFC API (Chrome Android only, requires HTTPS)
async function writeNDEF() {
    try {
        const ndef = new NDEFReader();
        await ndef.write({
            records: [
                { recordType: "url", data: "https://cupass.com" }
            ]
        });
        console.log("NDEF written successfully");
    } catch (err) {
        console.error("Write failed:", err);
    }
}

// Writing multiple records:
await ndef.write({
    records: [
        { recordType: "text", data: "Hello from CardWise" },
        { recordType: "url", data: "https://cupass.com" }
    ]
});

5. Writing NDEF — PC/SC with pyscard (ACR122U)

# Requires pyscard installed
from smartcard.System import readers
from smartcard.util import toBytes

def write_ndef_to_mifare(ndef_bytes):
    """Write NDEF message to NTAG via ACR122U."""
    r = readers()
    conn = r[0].createConnection()
    conn.connect()

    # Step 1: Authenticate (for MIFARE Classic; NTAG needs no auth)
    # Step 2: Write NDEF TLV: [0x03] [length] [NDEF message] [0xFE]
    tlv = bytes([0x03, len(ndef_bytes)]) + ndef_bytes + bytes([0xFE])

    # Step 3: Write page by page (NTAG: 4 bytes per page, starting page 4)
    for i in range(0, len(tlv), 4):
        page = 4 + i // 4
        data = tlv[i:i+4].ljust(4, b'\x00')
        # WRITE command: [0xFF, 0xD6, 0x00, page, 0x04, data]
        apdu = [0xFF, 0xD6, 0x00, page, 0x04] + list(data)
        conn.transmit(apdu)
Capacity check before writing: Always verify the NDEF message fits on the tag. Use our NFC Capacity Calculator to check byte size. NTAG 213 = ~137 bytes, NTAG 215 = ~492 bytes, NTAG 216 = ~868 bytes available for NDEF.
Test this yourself: Our NDEF Writer Simulator lets you build NDEF messages interactively in the browser — add Text, URI, Smart Poster, MIME records, see the hex output, and verify your message fits before writing to a real tag.

Ready to write real tags? The NTAG215 sticker pack (50 tags for ~$7) is the most affordable way to practice NDEF writing — Check Price on Amazon. For a more professional look, the NTAG215 Business Cards 20-pack comes pre-formatted for contact sharing — Check Price on Amazon. Writing on metal surfaces? The Anti-Metal NTAG215 20-pack includes a ferrite layer so the tag works on laptops and cabinets — Check Price on Amazon.

6. Tag Formatting and Write Protection

OperationNTAGMIFARE ClassicDESFire
NDEF Pre-formattedYes (factory)No (must format)No (ISO file system)
Set Read-OnlyLock dynamic lock bytesSet access bits C=010Set file access rights
Password ProtectPWD_AUTH + PACKKey A or B per sectorAES auth per file
Irreversible LockWrite 0x00 to CFG lock bytesSet trailer access bits to deny writeChange key + forget it

Related Tools

NDEF Writer Simulator — Build NDEF messages in-browser | NDEF Parser — Verify your encoded messages | NFC Capacity Calculator — Check if it fits | Android NFC NDEF Guide