Someone had an NFC card for which I needed the data. There are a couple of ways I could have gotten it from him. One way was to ask him to install an app and scan the card. From the app, he could export the data and send it to me. I don’t generally like to ask people to install apps though. The other option was to make a web page and have him use the web page to scan the card. Key factors here is that he was using a Samsung Android phone and he uses Chrome as his primary driver. Chrome on Android has NFC support.
I needed to make the page in a hurry. It’s a single file web page. You can find it at this URL: https://j2inet.github.io/nfcreader/. You can view the source code completely through this link. If you are viewing this article from your computer and want to run the code on your phone, here’s a QR Code to that same URL.

The Interface
The interface is just a bit of HTML decorated with CSS attributes. We don’t need to explore the CSS to understand the interface. There are a couple of buttons for enabling and disabling a scanner, and a checkbox that, when checked, will cause the NFC card’s serial number to also be displayed.
<div>
<button id="scanButton" onclick="scanTag()">Scan for Tags</button>
</div>
<div>
<button id="stopScanButton" onclick="abortScan()" disabled>Abort Scan</button>
</div>
<div style="position:relative;">
<input type="checkbox" id="showSerialNumber" /> <label for="showSerialNumber">Show Serial Number</label>
</div>
<div id="scanResult"></div>
<div id="lastError"></div>
How does it work?
At the time of this writing, NFC support is still considered experimental. mozilla.org reports support for Chrome 89, Opera 63, Samsung Internet 15.0, and WebView 89. I only tested this in Samsung Internet, Samsung Internet Beta, and Chrome. I only experienced this working in Chrome. Tho check whether your browser supports this API, see if the window object defines NDEFReader. If it does not, then the device doesn’t support reading NFC. Note that it is possible for a browser to have software support for NFC while the device has no available NFC hardware. That’s not something that you can test for.
if (! ('NDEFReader' in window)) {
//This device doesn't have NFC capabilities.
document.getElementById('scanButton').disabled = true;
document.getElementById('stopScanButton').disabled = true;
document.getElementById('lastError').innerText = "Sorry, this app only works on devices capable of reading NFC"
}
Preparing to Read NFC
Before attempting to read the from the NFC card, you’ll need to create an object and set some handlers. You’ll need to create a new NDEFReader object. No parameters are required for its constructor. On the newly created object, add handlers for onreading and onreadingerror.
reader = new NDEFReader();
reader.onreadingerror = (e) => {
var lastError = document.getElementById('lastError');
lastError.innerText = e.toString();
}
reader.onreading = (e) => {
...
}
We will talk about the body of the onreading method shortly.
Initiating the Scan
You can initiate a scan just by calling the scan() method on the NDEFReader object with no parameters. I don’t suggest that though. You will probably want to pass an abort object to the method. This gives you a method to deactivate the scanner at will. For this purpose, I’ve created an AbortController instance.
ar abortController = new AbortController();
The scan method returns a promise. We can use this to know if something went wrong or if the application decided to terminate the scanning. The object returned in this promose is defined by us. On the object I return I may have an isError and reasonText object.
function scanTag() {
reader.scan({signal: abortController.signal})
.then(()=>{})
.catch((e)=>{
if(!e) {
return;
}
if(e.isError) {
console.error(e);
} else {
if(e.reasonText) {
console.info(e.reasonText);
}
}
})
document.getElementById('scanButton').disabled = true;
document.getElementById('stopScanButton').disabled = false;
}
The onreading handler is given an event object. That object has a few elements of concern to us. One is serialNumber, which is a string that contains the serial number for that specific NFC card. The other element is message, which contains a NDEFMessage object. That’s where we will find most of the data! The NDEFMessage object has a field called records; that is a list of the NDEF records written to the card. You may typically encounter NFC cards that only have one message on it, but it can have multiple messages. If you iterate through this object on a non-empty list, you will find one or more NDEFRecord objects. These are the fields from the record that I find to be the most important.
| Field | Explination |
recordType | The type of record. It could be the strings "empty", "text", "url", "smart-poster", "absolute-url", "mine", or "unknown". It could also be a custom domain name and custom type separated with a colon(:). |
mediaType | Returns the mime type of the record. |
data | Returns the raw byte data of the record’s payload. |
encoding | The encoding used for a text payload. This could be null. |
Iterating and Displaying the Records
For displaying the records, I build a few HTML objects and populate their innerText with the values. I optionally include the serial number to the card. I’m only displaying the record type and the text data. For the card that I needed someone to scan, I knew that this would be sufficient for my purposes. I displayed both the text representation of the data and the raw bytes themselves. Displaying the raw bytes was just a matter of converting the byte values to numeric strings. The browser provides a TextDecoder object for this purpose.
utf8decoder = new TextDecoder('utf8');
...
dataTextElement.innerText = utf8decoder.decode(record.data.buffer);
Here is the complete text for what happens when the card is detected.
reader.onreading = (e) => {
var rootResultElement = document.getElementById("scanResult");
rootResultElement.innerText = "";
var showSerialNumberChecked = document.getElementById('showSerialNumber').checked;
if(showSerialNumberChecked) {
var serialNumberElement = document.createElement('div');
serialNumberElement.class = 'serialNumber';
serialNumberElement.innerText = e.serialNumber;
rootResultElement.appendChild(serialNumberElement) ;
}
for(var i=0;i<e.message.records.length;++i)
{
var record = e.message.records[i];
var envelopeElement = document.createElement('div')
envelopeElement.className = 'ndfmessage';
var typeText = document.createElement('div');
typeText.className = 'recordType'
typeText.innerText = record.recordType;
var dataElement = document.createElement('div');
dataElement.className = 'ndefdata'
dataElement.innerText = bufferToString(record.data.buffer);
var dataTextElement = document.createElement('div');
dataTextElement.innerText = utf8decoder.decode(record.data.buffer);
envelopeElement.appendChild(typeText);
envelopeElement.appendChild(dataElement);
envelopeElement.appendChild(dataTextElement);
rootResultElement.appendChild(envelopeElement);
}
}
Posts may contain products with affiliate links. When you make purchases using these links, we receive a small commission at no extra cost to you. Thank you for your support.
Mastodon: @j2inet@masto.ai
Instagram: @j2inet
Facebook: @j2inet
YouTube: @j2inet
Telegram: j2inet
Twitter: @j2inet


One thought on “Reading NFC Cards from HTML”