Web NFC API Guide
Web NFC brings NFC tagNFC tagPassive unpowered device storing data, powered by reader's RF fieldView full → reading and writing directly to the browser — no native app, no app store, no install friction. Users tap a tag, the browser presents a permission prompt, and your web app receives the NDEF payload via a JavaScript API.
What Is the Web NFC API?
Web NFC is a W3C specification that exposes NFC hardware to web pages via the NDEFReader interface. It is part of the broader Project Fugu effort to close the gap between web and native capabilities on mobile platforms.
const reader = new NDEFReader();
await reader.scan();
reader.onreading = (event) => {
const { serialNumber, message } = event;
for (const record of message.records) {
console.log("Record type:", record.recordType);
console.log("MIME type:", record.mediaType);
console.log("Data:", record.data);
}
};
The NDEFReader handles both reading (scan()) and writing (write()). No separate writer class exists — the same object manages both operations.
Browser Support
| Browser | Platform | Support | Notes |
|---|---|---|---|
| Chrome 89+ | Android | Full | Requires HTTPS + user gesture |
| Chrome for Android | Android | Full | Foreground only |
| Safari | iOS | No | Apple has not implemented Web NFC |
| Firefox | All | No | Not planned |
| Samsung Internet | Android | 11.2+ | Same engine as Chrome |
| Edge | Android | Yes | Chromium-based |
Web NFC is currently a Chrome on Android exclusive. iOS users must use a native app (Core NFC) or a PWA with a native bridge.
Reading Tags
async function readTag() {
if (!('NDEFReader' in window)) {
console.error('Web NFC not supported');
return;
}
const reader = new NDEFReader();
try {
await reader.scan(); // triggers permission prompt on first use
reader.addEventListener('reading', ({ message, serialNumber }) => {
console.log('UID:', serialNumber);
for (const record of message.records) {
if (record.recordType === 'url') {
const decoder = new TextDecoder();
console.log('URL:', decoder.decode(record.data));
}
}
});
reader.addEventListener('readingerror', () => {
console.error('Cannot read tag data');
});
} catch (err) {
console.error('Error:', err);
}
}
Writing Tags
async function writeUrl(url) {
const writer = new NDEFReader();
await writer.write({
records: [{ recordType: 'url', data: url }]
});
console.log('Written successfully');
}
The write() method accepts an ndef-message object with an array of ndef-records. Supported recordType values: 'url', 'text', 'mime', 'smart-poster', 'absolute-url', 'empty', and custom external types.
Security Model
Web NFC enforces strict security constraints:
| Constraint | Reason |
|---|---|
| HTTPS only | Prevents man-in-the-middle injection |
| User gesture required to start scan | Blocks silent background scanning |
| Foreground document only | No background service workers |
| Explicit permission per origin | User controls which sites can read NFC |
| No access to tag UID by default | Privacy — UID uniquely identifies tag |
The permission model prevents nfc-enabled-device abuse: a rogue website cannot scan tags silently in the background. The serialNumber (UID) is only exposed during an active scan session that the user explicitly permitted.
Practical Patterns
Progressive enhancement: Feature-detect NDEFReader and fall back to a QR code scanner or manual input for unsupported browsers.
PWA integration: Combine Web NFC with a service worker and Web App Manifest for an installable, offline-capable tag management app — no app store required.
Use the NDEF Message Encoder to generate test payloads compatible with the Web NFC record format.
See also: Android NFC Programming Guide and How to Read and Write NFC Tags.