Near Field Communication (NFC) used to require native iOS or Android mobile applications. With the Web NFC API, modern web apps can now read and write physical NFC tags directly in the browserβno app store downloads needed!
Whether you are building digital business cards (vCards), smart event passes, Wi-Fi credentials sharing, or hardware tap-to-pay experiences, Web NFC unlocks a whole new dimension of web-to-physical interactions.
In this guide, we'll cover:
- π± Browser & device support
- π Reading data from NFC tags (
NDEFReader.scan) - βοΈ Writing text, URLs, and vCards (
NDEFReader.write) - π Locking tags to read-only (
makeReadOnly) - β οΈ Security & permission requirements
π± Browser & Hardware Requirements
Before writing code, keep in mind:
- Browser Support: Currently supported natively in Chrome for Android (v89+).
-
Protocol: Requires HTTPS (
https://) orlocalhostduring development. - Hardware: An Android device with an active NFC chip and standard NDEF-compatible NFC tags (e.g., NTAG213, NTAG215, NTAG216).
- Feature Detection Always verify browser support before prompting the user:
function checkNfcSupport() {
if ('NDEFReader' in window) {
console.log('β
Web NFC is supported!');
return true;
} else {
console.warn('β οΈ Web NFC is not available on this browser/device.');
return false;
}
}
- Reading NFC Tags To start scanning, instantiate NDEFReader and call .scan(). Must be triggered by a user action (e.g., a button click).
async function startScanning() {
try {
const ndef = new NDEFReader();
await ndef.scan();
console.log('π‘ NFC Scan started. Bring a tag close to your phone...');
// Event listener for reading NDEF messages
ndef.addEventListener('reading', ({ message, serialNumber }) => {
console.log(`β
Tag Detected! Serial Number: ${serialNumber}`);
for (const record of message.records) {
console.log(`Record Type: ${record.recordType}`);
console.log(`MIME Type: ${record.mediaType || 'N/A'}`);
// Handle Text Records
if (record.recordType === 'text') {
const textDecoder = new TextDecoder(record.encoding);
const textContent = textDecoder.decode(record.data);
console.log(`> Text Payload: ${textContent}`);
}
// Handle URL Records
else if (record.recordType === 'url') {
const textDecoder = new TextDecoder();
const urlContent = textDecoder.decode(record.data);
console.log(`> URL Payload: ${urlContent}`);
}
// Handle vCards / Custom MIME Types
else if (record.mediaType === 'text/vcard') {
const vcardText = new TextDecoder().decode(record.data);
console.log(`> vCard Content:\n${vcardText}`);
}
}
});
ndef.addEventListener('readingerror', () => {
console.error('β Error reading NFC tag. Try positioning the tag closer.');
});
} catch (error) {
console.error(`Error starting scan: ${error.message}`);
}
}
- Writing Data to NFC Tags Writing to an NFC tag replaces existing NDEF records with new payloads.
A. Writing a Simple URL or Text
async function writeUrlToTag(urlToSave) {
try {
const ndef = new NDEFReader();
await ndef.write({
records: [
{
recordType: 'url',
data: urlToSave
}
]
});
console.log('π Successfully wrote URL to NFC tag!');
} catch (error) {
console.error(`Write failed: ${error.message}`);
}
}
B. Writing a Digital Contact Card (vCard)
You can write vCards using mediaType: 'text/vcard':
async function writeVCardToTag(contact) {
const vcardPayload = [
'BEGIN:VCARD',
'VERSION:3.0',
`FN:${contact.name}`,
`TEL:${contact.phone}`,
`EMAIL:${contact.email}`,
`URL:${contact.website}`,
'END:VCARD'
].join('\r\n');
try {
const ndef = new NDEFReader();
await ndef.write({
records: [
{
recordType: 'mime',
mediaType: 'text/vcard',
data: new TextEncoder().encode(vcardPayload)
}
]
});
console.log('π vCard written to NFC card successfully!');
} catch (error) {
console.error(`vCard write error: ${error.message}`);
}
}
- Locking Tags (Making Them Read-Only) β οΈ Warning: Making a tag read-only is permanent and irreversible!
async function lockTagPermanently() {
const confirmed = confirm('Are you sure you want to permanently lock this tag? This cannot be undone!');
if (!confirmed) return;
try {
const ndef = new NDEFReader();
await ndef.makeReadOnly();
console.log('π Tag locked successfully. It can no longer be overwritten.');
} catch (error) {
console.error(`Failed to lock tag: ${error.message}`);
}
}
π‘οΈ Best Practices & Security
User Gesture Requirement: Scanning and writing must be triggered by an explicit user gesture (e.g., button click).
HTTPS Requirement: Modern Web NFC will fail on non-secure contexts (http://), except for local development (localhost).
Handling Timeout: Use AbortController to handle scan timeouts or cancel operations if the user navigates away.
const controller = new AbortController();
async function scanWithTimeout() {
const ndef = new NDEFReader();
await ndef.scan({ signal: controller.signal });
// Abort scan after 15 seconds if no tag is tapped
setTimeout(() => controller.abort(), 15000);
}
π Live Demo & Open Source Tools
Want to test Web NFC directly in your browser without writing boilerplates?
Check out NfcTool.org β a free suite of browser-based Web NFC tools for:
π Scanning & Inspecting NFC Tags
βοΈ Programming NFC Cards
π Locking & Formatting NDEF Payloads
π Generating NFC vCards & QR Codes
π¬ What are you building with Web NFC?
Are you experimenting with Web NFC for event check-ins, smart cards, inventory tags, or IoT devices? Share your thoughts and projects in the comments below! π
Top comments (0)