Web NFC, and writing to NFC tags

Espruino Bluetooth devices like Puck.js and Pixl.js contain a programmable NFC tag.

In most cases you'd program them using NRF.nfcURL to make a URL available, and then a user can scan the device to go to that URL.

However, two way communications are possible by emulating an NFC tag on Puck.js.

To make a complete system, we'll write code for Puck.js that emulates a tag, and will then send text to it from a Web Page with Web NFC.

Note: More information is available on the Espruino NFC Tag Module page and the NRF NFC documentation.


Upload the following code to Puck.js:

var data = new Uint8Array(10+64);
var header = NRF.nfcStart();
var written = false;
data.set(header,0); // NFC device header
data.set([0,0,0xE1,0x10,(data.length-10)/8,0,0,3,0,0xFe], 0x0A); // NDEF tag header
// 0,0,e1
NRF.on('NFCrx', function(rx) {
  var idx = rx[1]*4;
  switch(rx[0]) {
    case 0x30: //command: READ
      NRF.nfcSend(new Uint8Array(data.buffer, idx, 16));
    case 0xa2: //command: WRITE
      written = true;
      if(idx > data.length) {
      } else {
        data.set(new Uint8Array(rx, 2, 4), idx);
    default:   //just, re-enable rx
NRF.on("NFCoff",function() {
  if (written)
    onWritten(E.toString(new Uint8Array(data.buffer,26,data[21]-3)));
  written = false;

function onWritten(data) {
  console.log("NFC written", data);
  var colors = {
    red : 1,
    green : 2,
    blue : 4,
  // Only light LEDs if we actually have 3 LEDs! Allows Pixl.js upload
  if (colors[data] && global.LED1 && global.LED2 && global.LED3) {
    digitalWrite([LED3,LED2,LED1], colors[data]);
    setTimeout(function() {
      digitalWrite([LED3,LED2,LED1], 0);

This will create a simple 64 byte NFC tag, and when the NFC writer is removed from within range of the Espruino device, Espruino will call onWritten with the new tag information if the tag had been modified.

On Puck.js this'll light up the red, green or blue LEDs, but on Pixl.js it'll just display the text that was written.

Web Page

First, check out this page for instructions on enabling Web NFC in your browser.

Then you can click the 'try now' link at the bottom of the page below to try it out:

if (typeof NDEFReader==="undefined") {
  document.write("NDEFReader is not supported on this browser<br/>");

const ndef = new NDEFReader();
ndef.onreading = event => {
  console.log("NFC", event);

function start() {
  /* Starting a scan stops Android from
  moving away from the Chrome window when a tag is found*/
  ndef.scan().then(_ => {
    // hide 'start' button
    document.querySelector("#startButton").style.display = "none";
    document.querySelector("#buttons").style.display = "block";

// If we already have permission, start right up!
// Otherwise we need the user to press a button
navigator.permissions.query({ name: "nfc" }).then(p => {
  if (p.state === "granted") start();

function send(msg) {
  ndef.write(msg).then(_=>console.log("Written ",msg));
<button id="startButton" onclick="start()">Start!</button>
<div id="buttons" style="display:none">
<button onclick="send('red')">Red</button>
<button onclick="send('green')">Green</button>
<button onclick="send('blue')">Blue</button>

Simply click one of the buttons while in NFC range of the Puck.js (eg your phone buzzes), and when you exit NFC range of the device the relevant LED will light.

Note: Old versions of Web NFC used NDEFWriter. If you get a ndef.write is not a function error, you'll need to update your browser.

Android app

This page is auto-generated from GitHub. If you see any mistakes or have suggestions, please let us know.