Near Field Communication (NFC) enables wireless communication between two devices at close proximity, usually less than a few centimeters. NFC is an international standard (ISO/IEC 18092) defining an interface and protocol for simple wireless interconnection of closely coupled devices operating at 13.56 MHz. The hardware standard is defined in [[[NFC-STANDARDS]]].

This document defines an API to enable selected use-cases based on NFC technology. The current scope of the specification is NDEF. Other NFC technologies may be supported in the future.

Implementers need to be aware that this specification is considered unstable. Implementers who are not taking part in the discussions will find the specification changing out from under them in incompatible ways. Vendors interested in implementing this specification before it eventually reaches the Candidate Recommendation phase should subscribe to the repository on GitHub and take part in the discussions.

This document defines conformance criteria that apply to a single product: the UA (user agent) that implements the interfaces it contains.

Introduction

NFC user scenarios can be grouped as follows:

NFC works using magnetic induction, meaning that the reader (an active, powered device) will emit a small electric charge which then creates a magnetic field. This field powers the passive device which turns it into electrical impulses to communicate data. Thus, when the devices are within range, a read is always performed (see NFC Analog Specification and NFC Digital Protocol, NFC Forum, 2006). The peer-to-peer connection works in a similar way, as the device periodically switches into a so-called initiator mode in order to scan for targets, then later to fall back into target mode. If a target is found, the data is read the same way as for tags.

As NFC is based on existing RFID standards, many NFC chipsets support reading RFID tags, but some of these are only supported by single vendors and not part of the NFC standards. As such, this document specifies ways to interact with the NFC Data Exchange Format (NDEF).

Terminology and conventions

The Augmented Backus-Naur Form (ABNF) notation used is specified in [[RFC5234]].

Security related terms

The term expressed permission refers to an act by the user, e.g. via user interface or setting or host device platform features, using which the user approves the permission of a browsing context to access the given functionality.

The term ask for forgiveness refers to some form of unobtrusive notification that informs the user of an operation while it is running. UAs SHOULD provide the user with means to ignore similar future operations from the same origin and advertise this to the user.

The term prearranged trust relationship means that the UA has already established a trust relationship for a certain operation using a platform specific mechanism, so that an expressed permission from the user is not any more needed. See also this section in the Security and Privacy document.

The term obtain permission for a certain operation indicates that the UA has either obtained expressed permission, or asked for forgiveness, or ensured a prearranged trust relationship exists.

The Web NFC permission name is defined as "`nfc`".

NFC specific terms

NFC stands for Near Field Communications, a short-range wireless technology operating at 13.56 MHz which enables communication between devices at a distance less than 10 cm. The NFC communications protocols and data exchange formats, and are based on existing radio-frequency identification (RFID) standards, including ISO/IEC 14443 and FeliCa. The NFC standards include ISO/IEC 18092[5] and those defined by the NFC Forum. See NFC Forum Technical Specifications for a complete listing.

An NFC adapter is the software entity in the underlying platform which provides access to NFC functionality implemented in a given hardware element (NFC chip). A device may have multiple NFC adapters, for instance a built-in one, and one or more attached via USB.

An NFC tag is a passive NFC device. The NFC tag is powered by magnetic induction when an active NFC device is in proximity range. An NFC tag contains a single NDEF message.

The way of reading the message may happen through proprietary technologies, which require the reader and the tag to be of the same manufacturer. Implementations are expected to encapsulate this.

An NFC peer is an active, powered device, which can interact with other devices in order to exchange data using NFC.

An NFC device is either an NFC peer, or an NFC tag.

NDEF is an abbreviation for NFC Forum Data Exchange Format, a lightweight binary message format that is standardized in [[!NFC-NDEF]].

An NDEF message encapsulates one or more application-defined NDEF records. NDEF messages can be stored on an NFC tag or exchanged between NFC-enabled devices.

The term NFC content denotes all bytes sent to or received from an NFC tag or an NFC peer. In the current API it is synonym to NDEF message.

The NFC Standard

NFC is standardized in the NFC Forum and described in [[NFC-STANDARDS]].

NDEF compatible tag types

The NFC Forum has mandated the support of five different tag types to be operable with NFC devices. The same is required on operating systems, such as Android.

In addition to that, the MIFARE Standard specifies a way for NDEF to work on top of the older MIFARE Standard, which may be optionally supported by implementers.

A note about the NDEF mapping can be found here: MIFARE Classic as NFC Type MIFARE Classic Tag

  1. NFC Forum Type 1: This tag is based on the ISO/IEC 14443-3A (NFC-A). The tags are rewritable and can be configured to become read-only. Memory size can be between `96` bytes and `2` Kbytes. Communication speed is `106` kbit/sec. In contrast to all other types, these tags have no anti-collision protection for dealing with multiple tags within the NFC field.
  2. NFC Forum Type 2: This tag is based on the ISO/IEC 14443-3A (NFC-A). The tags are rewritable and can be configured to become read-only. Memory size can be between `48` bytes and `2` Kbytes. Communication speed is `106` kbit/sec.
  3. NFC Forum Type 3: This tag is based on the Japanese Industrial Standard (JIS) X 6319-4 (ISO/IEC 18092), commonly known as FeliCa. The tags are preconfigured to be either rewritable or read-only. Memory is `2` kbytes. Communication speed is `212` kbit/sec or `424` kbit/s.
  4. NFC Forum Type 4: This tag is based on the ISO/IEC 14443-4 A/B (NFC A, NFC B) and thus supports either NFC-A or NFC-B for communication. On top of that the tag may optionally support ISO-DEP (Data Exchange Protocol defined in ISO/IEC 14443 (ISO/IEC 14443-4:2008 Part 4: Transmission protocol). The tags are preconfigured to be either rewritable or read-only. Variable memory, up to `32` kbytes. Supports three different communication speeds `106` or `212` or `424` kbit/s.
  5. NFC Forum Type 5: This tag is based on ISO/IEC 15693 (NFC-V) and allows reading and writing an NDEF message on a ISO/IEC 15693 RF tag that is accessible by long range RFID readers as well. The NFC communication is limited to short distance and may use the Active Communication Mode of ISO/IEC 18092 where the sending peer generates the field which balances power consumption and improves link stability. Variable memory, up to `64` kbytes. Communication speed `26.48` kbit/s
  6. MIFARE Standard: This tag, often sold under the brand names MIFARE Classic or MIFARE Mini, is based on the ISO/IEC 14443-3A (also known as NFC-A, as defined in ISO/IEC 14443-3:2011, Part 3: Initialization and anticollision). The tags are rewritable and can be configured to become read-only. Memory size can be between `320` and `4` kbytes. Communication speed is `106` kbit/sec.

    MIFARE Standard is a not an NFC Forum type and can only be read by devices using NXP hardware. Support for reading and writing to tags based on the MIFARE Standard is thus non-nominative, but the type is included due to the popularity and use in legacy systems.

In addition to data types standardized for NDEF records by the NFC Forum, many commercial products such as bus cards, door openers may be based on the MIFARE Standard which requires specific NFC chips (same vendor of card and reader) in order to function.

Card emulation mode capabilities also depend on the NFC chip in the device. For payments, a Secure Element is often needed.

The NDEF record and fields

An NDEF record is a part of an NDEF message. Each record is a binary structure that contains a data payload, as well as associated type information. In addition to this, it includes information about how the data is structured, like payload size, whether the data is chunked over multiple records etc.

A generic record looks like the following:

Only the first three bytes (lines in figure) are mandatory. First the header byte, followed by the TYPE LENGTH field and PAYLOAD LENGTH field, which may both be zero.

The TNF field (bit `0-2`, type name format) indicates the format of the type name and is often exposed by native NFC software stacks. The field can take binary values denoting the following NDEF record payload types:
TNF value Description
0 Empty record
1 NFC Forum [=well-known type record=]
2 MIME type record
3 Absolute-URL record
4 NFC Forum external type record
5 Unknown record
6 Unchanged record
7 Reserved for future use

The IL field (bit `3`, id length) indicates whether an ID LENGTH field is present. If the IL field is `0`, then the ID field is not present either.

The SR field (bit `4`, short record) indicates a short record, one with a payload length <= `255` bytes. Normal records can have payload lengths exceeding `255` bytes up to a maximum of `4` GB. Short records only use one byte to indicate length, whether as normal records use `4` bytes (`2``32``-1` bytes).

The CF field (bit `5`, chunk flag) indicates whether the payload is chunked across multiple records.

Web NFC turns all received chunked records into logical records and transparently chunks sent payload when that is needed.

The ME field (bit `6`, message end) indicates whether this record is the last in the NDEF message.

The MB field (bit `7`, message begin) indicates whether this record is the first of the NDEF message.

The TYPE LENGTH field is an unsigned 8-bit integer that denotes the byte size of the TYPE field.

The TYPE field is a globally unique and maintained identifier that describes the type of the PAYLOAD field in a structure, encoding and format dictated by value of the TNF field.

The [[[!NFC-RTD]]] requires that the TYPE field names MUST be compared in case-insensitive manner.

The ID LENGTH field is an unsigned 8-bit integer that denotes the byte size of the ID field.

The ID field is an identifier in the form of a URI reference ([[RFC3986]]) that is unique, and can be absolute of relative (in the latter case the application must provide a base URI). Middle and terminating chunk records MUST NOT have an ID field, other records MAY have it.

The PAYLOAD LENGTH field denotes the byte size of the PAYLOAD field. If the SR field is `1`, its size is one byte, otherwise 4 bytes, representing an 8-bit or 32-bit unsigned integer, respectively.

The PAYLOAD field carries the application bytes. Any internal structure of the data is opaque to NDEF. Note that in certain cases discussed later, this field MAY contain an NDEF message as data.

NDEF Record types

Empty NDEF record (TNF 0)

An empty record's' TYPE LENGTH field, ID LENGTH field and PAYLOAD LENGTH field MUST be `0`, thus the TYPE field, ID field and PAYLOAD field MUST NOT be present.

Well-known type records (TNF 1)

The NFC Forum has standardized a small set of useful sub record types in [[NFC-RTD]] (Resource Type Definition specifications) called well-known types, for instance text, URL, media and opaque binary data. In addition, there are record types designed for more complex interactions, such as smart posters (containing optional embedded records for url, text, signature and actions), and handover records.

These sub record types can be stored in the well-known type record.

The type information stored in the TYPE field can be of two kinds:
  • NFC Forum global type that are defined and managed by the NFC Forum and usually start with uppercase letter. Examples: "`T`" for text, "`U`" for URL, "`Sp`" for smart poster, "`Sig`" for signature, "`Hc`" for handover carrier, "`Hr`" for handover request, "`Hs`" for handover select, etc.
  • NFC Forum local type that are defined by the NFC Forum or by an application, and always start with lowercase letter or a number. Those are usually short strings that are unique only within the local context of the containing record. They are used when types meaning doesn't matter outside of the local context of the containing record and when storage usage is a hard constraint. See Smart poster for an example on how local types are used.

    A [=local type=] is thus defined in terms of a containing record type, and thus doesn't need any namespacing. For this reason the same local type name can be used within another record type with different meaning and different payload type.

Text record
The Text record is a [=well-known type record=] that is defined in the [[NDEF-TEXT]] specification. The TNF field is `1` and the TYPE field is "`T`" (`0x54`). The first byte of the PAYLOAD field is a status byte, followed by the [=language tag=] in US-ASCII encoding. The rest of the payload is the actual text, encoded either in UTF-8 or UTF-16, as indicated by the status byte as follows:
  • Bits 0 to 5 define the length of the [=language tag=].
  • Bit 6 is `0`.
  • If bit 7 if set, means the payload is encoded in UTF-8, otherwise in UTF-16.
URI record

URI record is defined in [[NDEF-URI]]. The TNF field is `1` and the TYPE field is "`U`" (`0x55`). The first byte of the PAYLOAD field is a URI identifier code, in fact an index in an abbreviation table where the values are prepended to the rest of the URI. For instance the value `0` denotes no prepending, `1` denotes "`http://www.`", `0x04` denotes "`https://`"" and so on. The rest of the payload contains the rest of the URI as a UTF-8 string (and if the first byte is `0`, then it denotes the whole URI).

The URI is defined in [[RFC3987]] and in fact is a UTF-8 encoded IRI that can be a URN or a URL.

Smart poster record

Smart poster is defined in [[NDEF-SMARTPOSTER]] as an NDEF record that contains an NDEF message as payload, which may contain several records: a mandatory URI record that refers to a content, and additional optional records related to the content: a title record (a Text record), one or more icon records, a type record, a size record and an action record.

Icon records are MIME type records. If multiple icon records are included, readers SHOULD select only one of them to display.

The type record has [=local type name=] "`t`" specific to smart poster and the PAYLOAD field contains a UTF-8 encoded MIME type for the content referred to by the URI record.

The size record has [=local type name=] "`s`" specific to smart poster and the PAYLOAD field contains a 4-byte 32 bit unsigned integer that denotes the size of the object referred to by the URL in the URI record.

The action record has [=local type name=] "`act`" specific to smart poster and the PAYLOAD field contains a single byte, whose value has the following meaning:

Value Description
0 Do the action
1 Save for later
2 Open for editing
3..0xFF Reserved for future use

The action record is optional and there is no default action on the smart poster content if the action record is missing.

At the time of NDEF standardization the value `0` ("do the action") was meant for use cases like send an SMS, make a call or launch browser. Similarly, the value `1`, ("save the content for later processing") was meant for use cases like store the SMS in inbox, save the URL in bookmarks, or save the phone number to contacts. Also, the value `2` ("open for editing") was meant to open the smart poster content with a default application for editing.

Implementations don't need to implement any standardized behavior for the actions defined here. In this API it's up to the applications what actions they define (that may include the use cases above). However, Web NFC just provides the values.

The example below shows a smart poster record that embeds a text and a URL record.
Signature records

NDEF Signature is defined [[NDEF-SIGNATURE]]. Its TYPE field contains "`Sig`" (`0x53`, `0x69`, `0x67`) and its PAYLOAD field contains version, signature and a certificate chain. In this version of the API, Web NFC only provides the raw byte content of the payload (see this issue).

Handover records

NFC handover is defined [[NFC-HANDOVER]] and the corresponding message structure that allows negotiation and activation of an alternative communication carrier, such as Bluetooth or WiFi. The negotiated communication carrier would then be used (separately) to perform certain activities between the two devices, such as sending photos to the other device, printing to a Bluetooth printer or streaming video to a television set. Web NFC does not support this at the moment (see this issue.

MIME type records (TNF 2)

The MIME type records are records that store binary data with associated MIME type.

Absolute-URL records (TNF 3)

In absolute-URL records the TYPE field contains the absolute-URL string, and not the payload.

NOTE: Some platforms, like Windows Phone have stored additional data in the payload, but any payload data in these records are ignored by other platforms such as Android. On Android, reading such a record, will attempt to load the URL in Chrome and it is as such not intended for client applications.

External type records (TNF 4)

The NFC Forum external type records are for application specified data types and are defined in [[[NFC-RTD]]].

The external type is a URN with the prefix `"urn:nfc:ext:"` followed by the name of the owner domain, adding a colon, then a non-zero type name, for instance `"urn:nfc:ext:w3.org:atype"`, stored as `"w3.org:atype"` in the TYPE field.

Unknown type records (TNF 5)

The unknown records are records that store opaque data without associated MIME type, meaning that the `application/octet-stream` default MIME type MAY be assumed. The [[NFC-NDEF]] specification recommends that NDEF parsers store or forward the payload without processing it.

Unchanged type records (TNF 6)

The unchanged records are record chunks of a chunked data set, and is used for any, but the first record. A chunked payload is spread across multiple NDEF records that undergo the following rules:
  • The initial chunk record has the CF field set, its TYPE field set to the type of the whole chunked payload and its ID field MAY be set to an identifier used for the whole chunked payload. Its PAYLOAD LENGTH field denotes the size of the payload chunk in this record only.
  • The middle chunk records have the CF field set, have the same ID field as the first chunk, their TYPE LENGTH field and IL field MUST be `0` and their TNF field MUST be `6` (unchanged).
  • The terminating chunk record has this flag cleared, and in rest undergo the same rules as the middle chunk records.
  • A chunked payload MUST be contained in a single NDEF message, therefore the initial and middle chunk records cannot have the ME field set.
First record:
Intermediate record:
Last record:

Any implementation of Web NFC MUST transparently expose chunked records as single logical records.

Use Cases

A few Web NFC user scenarios are described in the Use Cases document. These user scenarios can be grouped by criteria based on security, privacy and feature categories, resulting in generic flows as follows.

Reading an NFC tag

  1. Reading an NFC tag containing an NDEF message, when the {{Document}} of the top-level browsing context using Web NFC is visible. For instance, a web page instructs the user to tap an NFC tag, and then receives information from the tag.
  2. Reading an NFC tag containing other than NDEF message, when the {{Document}} of the top-level browsing context using Web NFC is visible.
  3. Reading an NFC tag when no {{Document}} using Web NFC is visible.

    This use case is not supported in this version of the specification.

Writing to an NFC tag

The user opens a web page which can write an NFC tag. The write operations may be one of the following:
  1. Writing to an empty NFC tag.
  2. Writing to an NFC tag which already contains a NDEF message with a different record identifier (i.e. overwriting a web-specific tag).
  3. Writing to an NFC tag which already contains a NDEF message with the same record identifier (i.e. updating own tag).
  4. Writing to other, writable NFC tags (i.e. overwriting a generic tag).

Note that an NFC write operation to an NFC tag always involves also a read operation.

Pushing data to an NFC peer device

In general, pushing data to another NFC capable device requires that on the initiating device the user would first have to navigate to a web site. The user would then touch the device against another Web NFC equipped device, and data transfer would occur.

On the receiving device the UA will dispatch the content to an application registered and eligible to handle the content, and if that application is a browser which has a {{Document}} of the top-level browsing context visible with active {{NDEFReader}}, then the content is delivered to the page through the NDEFReadingEvent.

Handover to another wireless connection type

NFC supports handover protocols to Bluetooth or WiFi connectivity for the purpose of larger volume data transfer. The user touches another NFC capable device, and as a result configuration data is sent for a new Bluetooth or WiFi connection, which is then established between the devices.

This use case is not supported in this version of the specification.

Payment scenarios

Payment scenarios with Web NFC generally do not refer to supporting the payment process itself, but associating the payment status with a web page in order to have secondary actions. For instance, the user buys goods in a store, and payment options include contactless payment using NFC technology. In general, touching the device to the point of sales terminal receiver area will result in a transaction between the secure element from the device and the point of sales terminal. With Web NFC, if the user navigates to a web site before paying, there may be interaction with that site regarding the payment, e.g. the user could get points and discounts, or get delivered application or service specific data (e.g. tickets, keys, etc) to the device.

This use case is not supported in this version of the specification.

Support for multiple NFC adapters

Users may attach one or more external NFC adapters to their devices, in addition to a built-in adapter. Users may use either NFC adapter.

Features

High level features for the Web NFC specification include the following:
  1. Support devices with single or multiple NFC adapters. If there are multiple adapters present when invoking an NFC function then the UA operates all NFC adapters in parallel.
  2. Support communication with active (powered devices such as readers, phones) and passive (smart cards, tags, etc) devices.
  3. Allow users to act on (e.g. read, write or transceive) discovered NFC devices (passive and active), as well as access the payload which were read in the process as NDEF messages.
  4. Allow users to write a payload via NDEF records to compatible devices, such as writable tags, when they come in range, as NDEF messages.
  5. [future] Allow manual connection for various technologies such as NFC-A and NFC-F depending on the secondary device.
  6. [future] Allow NFC handover to Bluetooth or WiFi.
  7. [future] Allow card emulation with secure element or host card emulation.
This specification makes a few simplifications in what use cases and data types Web NFC can handle:

Examples

This section shows how developers can make use of the various features of this specification.

Feature support

Detecting if Web NFC is supported can be done by checking NDEFReader and/or NDEFWriter objects. Note that this does not guarantee that NFC hardware is available.

      if ('NDEFReader' in window) { /* ... Scan NDEF Tags */ }
      if ('NDEFWriter' in window) { /* ... Write NDEF Tags */ }
    

Push a text string to either a tag or peer

Pushing a text string to any kind of device is straightforward. Options can be left out, as they default to pushing to both tags and peers.

      const writer = new NDEFWriter();
      writer.push(
        "Hello World"
      ).then(() => {
        console.log("Message pushed.");
      }).catch(error => {
        console.log(`Push failed :-( try again: ${error}.`);
      });
    

Push a text string to a peer device

It is possible to restrict to which devices (tags or peers) data should be pushed. Below push is specified only to peers, and thus, no data is pushed when the user taps a tag.

      const writer = new NDEFWriter();
      writer.push(
        "Text meant for peers only", { target: "peer" }
      ).then(() => {
        console.log("Message pushed.");
      }).catch(_ => {
        console.log("Push failed :-( try again.");
      });
    

Push a URL to either a tag or peer

In order to push an NDEF record of URL type, simply use NDEFMessage.

      const writer = new NDEFWriter();
      writer.push({
        records: [{ recordType: "url", data: "https://w3c.github.io/web-nfc/" }]
      }).then(() => {
        console.log("Message pushed.");
      }).catch(_ => {
        console.log("Push failed :-( try again.");
      });
    

Read data from tag, and write to empty ones

This example shows reading various different kinds of data which can be stored on a tag. If the tag is unformatted or contains an empty record, a text message is written with the value "Hello World".

      const reader = new NDEFReader();
      await reader.scan();
      reader.onreading = event => {
        const message = event.message;

        if (message.records.length == 0 ||     // unformatted tag
            message.records[0].recordType == 'empty' ) {  // empty record
          const writer = new NDEFWriter();
          writer.push({
            records: [{ recordType: "text", data: 'Hello World' }]
          });
          return;
        }

        const decoder = new TextDecoder();
        for (const record of message.records) {
          switch (record.recordType) {
            case "text":
              const textDecoder = new TextDecoder(record.encoding);
              console.log(`Text: ${textDecoder.decode(record.data)} (${record.lang})`);
              break;
            case "url":
              console.log(`URL: ${decoder.decode(record.data)}`);
              break;
            case "mime":
              if (record.mediaType === "application/json") {
                console.log(`JSON: ${JSON.parse(decoder.decode(record.data))}`);
              }
              else if (record.mediaType.startsWith('image/')) {
                const blob = new Blob([record.data], {type: record.mediaType});

                const img = document.createElement("img");
                img.src = URL.createObjectURL(blob);
                img.onload = () => window.URL.revokeObjectURL(this.src);

                document.body.appendChild(img);
              }
              else {
                console.log(`Media not handled`);
              }
              break;
          }
        }
      };
    

Save and restore game progress with another device

Filtering of relevant data sources can be done by the use of the NDEFScanOptions. Below we accept the record identifier URL with "`/mypath/mygame/`" in its path from "`mygame.com`" domain and its subdomains. When we read the data, we immediately update the game progress by issuing a push with a custom NDEF data layout.

The example allows reading and pushing to both peers and tags, whichever one is tapped first.

      const reader = new NDEFReader();
      await reader.scan({ id: "https://mygame.com/mypath/mygame" });
      reader.onreading = async event => {
        console.log(`Game state: ${ JSON.stringify(event.message.records) }`);

        const encoder = new TextEncoder();
        const newMessage = {
          records: [{
            id: "/mypath/mygame/update",
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              level: 3,
              points: 4500,
              lives: 3
            }))
          }]
        };
        const writer = new NDEFWriter();
        await writer.push(newMessage);
        console.log("Pushed message");
      };
    

Push and read JSON (serialized and deserialized)

Storing and receiving JSON data is easy with serialization and deserialization.

      const reader = new NDEFReader();
      await reader.scan({
        mediaType: "application/*json"
      });
      reader.onreading = event => {
        const decoder = new TextDecoder();
        for (const record of event.message.records) {
          if (record.mediaType === 'application/json') {
            const json = JSON.parse(decoder.decode(record.data));
            const article =/^[aeio]/i.test(json.title) ? "an" : "a";
            console.log(`${json.name} is ${article} ${json.title}`);
          }
        }
      };

      const writer = new NDEFWriter();
      const encoder = new TextEncoder();
      writer.push({
        records: [
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Benny Jensen",
              title: "Banker"
            }))
          },
          {
            recordType: "mime",
            mediaType: "application/json",
            data: encoder.encode(JSON.stringify({
              name: "Zoey Braun",
              title: "Engineer"
            }))
          }]
      });
    

Write data to tag and print out existing data

Pushing data to a tag requires tapping it. If existing data should be read during the same tap, we need to set the [=NDEFPushOptions/ignoreRead=] property to `false`.

      const reader = new NDEFReader();
      reader.scan().then(() => {

        reader.onreading = event => {
          const decoder = new TextDecoder();
          for (const record of event.message.records) {
            console.log("Record type:  " + record.recordType);
            console.log("MIME type:    " + record.mediaType);
            console.log("=== data ===\n" + decoder.decode(record.data));
          }
        };

        const writer = new NDEFWriter();
        return writer.push("Pushing data is fun!", { target: "tag", ignoreRead: false });

      }).catch(error => {
        console.log(`Push failed :-( try again: ${error}.`);
      });
    

Stop listening to NDEF messages

Read NDEF messages for 3 seconds by using [=NDEFScanOptions/signal=].

      const reader = new NDEFReader();
      const controller = new AbortController();

      await reader.scan({ signal: controller.signal });
      reader.onreading = event => {
        console.log("NDEF message read.");
      };

      controller.signal.onabort = event => {
        console.log("We're done waiting for NDEF messages.");
      };

      // Stop listening to NDEF messages after 3s.
      setTimeout(() => controller.abort(), 3000);
    

Push a smart poster message

      const writer = new NDEFWriter();
      writer.push({ records: [
        {
          recordType: "smart-poster",
          data: { records: [
            {
              recordType: "url",
              data: "https://my.org/content/19911"
            },
            {
              recordType: "t", // smart poster type, a local type to Sp
              data: "image/gif"
            },
            {
              recordType: "text",
              data: "Funny dance"
            },
            {
              recordType: "s",  // size, a local type to Sp
              data: 4096  // byte size of the content at the URL above
            },
            {
              recordType: "act",  // action, a local type to Sp
              data: 0  // do the action, in this case open in the browser
            }
          ]}
        }
      ]});
    

Read an external record with an NDEF message as payload

External type records can be used to create application defined records. These records may contain an NDEF message as payload, with its own NDEF records, including local types that are used in the context of the application.

Note that the smart poster record type also contains an NDEF message as payload.

As NDEF gives no guarantee on the ordering of records, using an external type record with an NDEF message as payload, can be useful for encapsulating related data.

This example shows how to read an external record for social posts, which contains an NDEF message, containing a text record and a record with the local type "act" (action), with definition borrowed from smart poster, but used in local application context.

      const reader = new NDEFReader();
      await reader.scan({ recordType: "example.com:sp" });
      reader.onreading = event => {
        const socialPost = event.message.records[0];
        if (!socialPost) {
          return;
        }

        let action;
        let text = "";

        const decoder = new TextDecoder();
        for (let record of socialPost.toRecords()) {
          switch (record.recordType) {
            case "text":
              text = decoder.decode(record.data);
              break;
            case "act":
              action = record.data.getUint8(0);
              break;
          }
        }

        switch (action) {
          case 0: // do the action
            console.log(`Post "${text}" to timeline`);
            break;
          case 1: // save for later
            console.log(`Save "${text}" as a draft`);
            break;
          case 2: // open for editing
            console.log(`Show editable post with "${text}"`);
            break;
        }
      };
    

Push an external record with an NDEF message as payload

External type records can be used to create application defined records that may even contain an NDEF message as payload.

      const writer = new NDEFWriter();
      writer.push({ records: [
        {
          recordType: "example.game:a",
          data: {
            records: [
              {
                recordType: "url",
                data: "https://example.game/42"
              },
              {
                recordType: "text",
                data: "Game context given here"
              },
              {
                recordType: "mime",
                mediaType: "image/png"
                data: getImageBytes(fromURL);
              }
            ]
          }
        }
      ]});
    

Push and read unknown records inside an external record

Unknown type records may be useful inside external type records as developers know what they represent and therefore can avoid specifying the mime type.

      const encoder = new TextEncoder();
      const writer = new NDEFWriter();
      writer.push({ records: [
        {
          recordType: "example.com:shoppingItem", // External record
          data: {
            records: [
              {
                recordType: "unknown", // Shopping item name
                data: encoder.encode("Food")
              },
              {
                recordType: "unknown", // Shopping item description
                data: encoder.encode("Provide nutritional support for an organism.")
              }
            ]
          }
        }
      ]});
    
      const reader = new NDEFReader();
      await reader.scan({ recordType: "example.com:shoppingItem" });
      reader.onreading = event => {
        const shoppingItemRecord = event.message.records[0];
        if (!shoppingItemRecord) {
          return;
        }

        const [nameRecord, descriptionRecord] = shoppingItemRecord.toRecords();

        const decoder = new TextDecoder();
        console.log("Item name: " + decoder.decode(nameRecord.data));
        console.log("Item description: " + decoder.decode(descriptionRecord.data));
      };
    

Data Representation

The NDEFMessage interface

The content of any NDEF message is exposed by the NDEFMessage interface:

      [Exposed=Window]
      interface NDEFMessage {
        constructor(NDEFMessageInit messageInit);
        readonly attribute FrozenArray<NDEFRecord> records;
      };

      dictionary NDEFMessageInit {
        required sequence<NDEFRecordInit> records;
      };
    

The records property represents a list of NDEF records defining the NDEF message.

The NDEFMessageInit dictionary is used to initialize a NDEF message.

The NDEFRecord interface

The content of any NDEF record is exposed by the NDEFRecord interface:

      [Exposed=Window]
      interface NDEFRecord {
        constructor(NDEFRecordInit recordInit);

        readonly attribute USVString recordType;
        readonly attribute USVString? mediaType;
        readonly attribute USVString id;
        readonly attribute DataView? data;

        readonly attribute USVString? encoding;
        readonly attribute USVString? lang;

        sequence<NDEFRecord> toRecords();
      };

      dictionary NDEFRecordInit {
        required USVString recordType;
        USVString mediaType;
        USVString id;

        USVString encoding;
        USVString lang;

        any data;
      };
    

A NDEFRecord object has the following internal slots:

Internal slot Initial value Description (non-normative)
[[\PayloadData]] Empty byte sequence. A byte sequence representing the whole or a subset of the PAYLOAD field data.

The mediaType property represents the MIME type of the NDEF record payload.

The recordType property represents the NDEF record types.

The id property represents the record identifier, which is an absolute or relative URL. The required uniqueness of the identifier is guaranteed by the generator, as such only absolute URLs based on the origin of the browsing content can be written using this specification.

The NFC NDEF specifications uses the terms "message identifier" and "payload identifier" instead of record identifier, but the identifier is tied to each record and not the message (collection of records), and it may be present when no payload is.

The encoding attribute represents the [=encoding/name|encoding name=] used for encoding the payload in the case it is textual data.

The lang attribute represents the [=language tag=] of the payload in the case that was encoded.

A language tag is a string that matches the production of a Language-Tag defined in the [[BCP47]] specifications (see the IANA Language Subtag Registry for an authoritative list of possible values). That is, a language range is composed of one or more subtags that are delimited by a U+002D HYPHEN-MINUS ("-"). For example, the 'en-AU' language range represents English as spoken in Australia, and 'fr-CA' represents French as spoken in Canada. Language tags that meet the validity criteria of [[RFC5646]] section 2.2.9 that can be verified without reference to the IANA Language Subtag Registry are considered structurally valid.

The data property represents the [[\PayloadData]] bytes of the NDEF Record.

The toRecords() method, when invoked, MUST return the result of running convert NDEFRecord.[[\PayloadData]] bytes with the NDEF Record.

The NDEFRecordInit dictionary is used to initialize an NDEF record with its record type recordType, and optional record identifier id and payload data data.

Additionally, there are additional optional fields that are only applicable for certain record types:
  • "mime": Optional MIME type mediaType.
  • "text": Optional [=encoding/label|encoding label=] encoding and [=language tag=] lang.

The mapping from data types of an NDEFRecordInit to NDEF record types is presented in the algorithmic steps which handle the data and described in the [[[#steps-receiving]]] and [[[#writing-or-pushing-content]]] sections.

To convert NDEFRecord.[[\PayloadData]] bytes given a |record:NDEFRecord|, run these steps:

  1. Let |bytes:byte sequence| be |record|.[[\PayloadData]].
  2. Let |recordType:record type| be the value of |record|'s recordType attribute.
  3. If the |recordType| value is "`smart-poster`", or an [=external type name=], then return the result of running parse records from bytes on |bytes|. Re-[= exception/throw =] any exceptions.
  4. Otherwise, [= exception/throw =] a {{"NotSupportedError"}} {{DOMException}} and abort these steps.

The record type string

This string defines the allowed record types for a NDEFRecord. The [[[#data-mapping]]] section describes how it is mapped to NDEF record types.

A set of known standardized values exists, but it is also possible for organizations to create their own custom [=external type names=].

The "empty" string
The value representing empty NDEFRecord.
The "text" string
The value representing a Text record.
The "url" string
The value representing a URI record.
The "smart-poster" string
The value representing a Smart poster record.
The "absolute-url" string
The value representing a absolute-URL record.
The "mime" string
The value representing a MIME type record.
The "unknown" string
The value representing an unknown record.
An external type name
A {{DOMString}} representing a custom type for the external type record. The type must follow the [=external type name=] ABNF.
            ext-type             = reg-name ":" custom-type
            custom-type          = 1*(ALPHA / DIGIT / other)

            DIGIT                = %x30-39
            ALPHA                = %x41-5A / %x61-7A   ; A-Z / a-z
            other                = "(" / ")" / "+" / "," / "-" / ":" / "=" /
                                   "@" / ";" / "$" / "_" / "!" / "*" / "'" / "."
          
The `reg-name` value is a [=host/registrable domain=] owned by the issuing organization, a "`:`" and a type, e.g. "`w3.org:member`". And additional ABNF exists for [=well-known type records=]:
            wkt-type             = (ALPHA / DIGIT) *(ALPHA / DIGIT / other)
          

The [[[NFC-RTD]]] defines every type in the [=well-known type records=] and external type records in terms of URNs, but only a subset of the URN is actually stored in the NDEF record's TYPE field, which corresponds to the above two ABNFs.

A local type name
A {{DOMString}} that MUST start with lowercase letter or a number, representing a type for a NFC Forum [=local type=], typically used in a record of an NDEFMessage that is the payload of a parent NDEFRecord, for instance in a smart poster. The context of the local type is the parent record whose payload is the NDEFMessage to which this record belongs. The value MUST NOT be equal to any other record types defined in this API.

Any implementation of Web NFC MUST transparently expose chunked records as single logical records, therefore unchanged records are not explicitly represented.

Two well-known types (including any NFC Forum local type and any NFC Forum global type) MUST be compared character by character in case-sensitive manner.

Two external types MUST be compared character by character, in case-insensitive manner.

The binary representation of any well-known type and external type MUST be written as a relative URI (RFC 3986), omitting the namespace identifier (NID) "`nfc`" and namespace specific string (NSS) "`wkt`" and "`ext`", respectively, i.e. omitting the "`urn:nfc:wkt:`" and "`urn:nfc:ext:`" prefixes. For instance, "`urn:nfc:ext:company.com:a" is stored as "`company.com:a`" and the well-known type of a Text record is "`urn:nfc:wkt:T`", but it is stored as "`T`".

Data mapping

The mapping from data types of an NDEFRecordInit to NDEF record types, as used in the [[[#writing-or-pushing-content]]] section is as follows:

[=recordType=] [=mediaType=] [=data=] record type [=TNF field=] [=TYPE field=]
"`empty`" unused unused Empty record 0 unused
"`text`" unused {{BufferSource}} or
{{DOMString}}
[=Well-known type record=] 1 "`T`"
"`url`" unused {{DOMString}} [=Well-known type record=] 1 "`U`"
"`smart-poster`" unused {{NDEFMessage}} [=Well-known type record=] 1 "`Sp`"
[=local type name=] unused {{BufferSource}} [=Local type=] record* 1 [=local type name=]
"`mime`" [= MIME type =] {{BufferSource}} MIME type record 2 [= MIME type =]
"`absolute-url`" unused {{DOMString}} url Absolute-URL record 3 [=Absolute-URL=]
[=external type name=] unused {{BufferSource}} or
{{NDEFMessage}}
External type record 4 [=external type name=]
"`unknown`" unused {{BufferSource}} [=Unknown record=] 5 unused

* A [=local type=] record has to be embedded with the NDEFMessage payload of another record.

The mapping from NDEF record types to NDEFRecord, as used for incoming NDEF messages described in the [[[#steps-receiving]]] section, is as follows.

record type [=TNF field=]
[=TYPE field=] [=recordType=] [=mediaType=]
[=Empty record=] 0 unused "`empty`" `undefined`
[=Well-known type record=] 1 "`T`" "`text`" `undefined`
[=Well-known type record=] 1 "`U`" "`url`" `undefined`
[=Well-known type record=] 1 "`Sp`" "`smart-poster`" `undefined`
[=Local type=] record* 1 [=local type name=] [=local type name=] `undefined`
[=MIME type record=] 2 [=MIME type=] "`mime`" The MIME type used in the NDEF record
[=Absolute-URL record=] 3 URL "`absolute-url`" `undefined`
[=External type record=] 4 [=external type name=] [=external type name=] `undefined`
Unknown record 5 unused "`unknown`" `undefined`

The NDEFReader and NDEFWriter objects

The objects provide a way for the browsing context to use NFC functionality. They allow for pushing NDEF messages to NFC tags or NFC peers within range, and to act on incoming NDEF messages either from an NFC tag or an NFC peer.
    typedef (DOMString or BufferSource or NDEFMessageInit) NDEFMessageSource;

    [SecureContext, Exposed=Window]
    interface NDEFWriter {
      constructor();

      Promise<void> push(NDEFMessageSource message, optional NDEFPushOptions options={});
    };

    [SecureContext, Exposed=Window]
    interface NDEFReader : EventTarget {
      constructor();

      attribute EventHandler onerror;
      attribute EventHandler onreading;

      Promise<void> scan(optional NDEFScanOptions options={});
    };

    [SecureContext, Exposed=Window]
    interface NDEFReadingEvent : Event {
      constructor(DOMString type, NDEFReadingEventInit readingEventInitDict);

      readonly attribute DOMString serialNumber;
      [SameObject] readonly attribute NDEFMessage message;
    };

    dictionary NDEFReadingEventInit : EventInit {
      DOMString? serialNumber = "";
      required NDEFMessageInit message;
    };
  

The NDEFMessageSource is a union type representing argument types accepted by the [=NDEFWriter/push()=] method.

The NDEFReadingEvent is the event being dispatched on new NFC readings. The serialNumber property represents the serial number of the device used for anti-collision and identification, or empty string in case none is available. The message is an NDEFMessage object.

NDEFReadingEventInit is used to initialize a new event with a serial number and the NDEFMessageInit data via the message member. If serialNumber is [= dictionary member/not present =] or is `null`, empty string will be used to init the event.

Though most tags will have a stable unique identifier (UID), not all have one and some tags even create a random number on each read. The serial number usually consists of 4 or 7 numbers, separated by `:`.

The NDEFWriter is an object used for writing data to NFC devices such as tags.

An {{NDEFWriter}} object has the following internal slots:

Internal Slot Initial value Description (non-normative)
[[\PushOptions]] `null` The {{NDEFPushOptions}} value for writer.
[[\PushMessage]] `null` The {{NDEFMessage}} to be written. It is initially unset.

The NDEFReader is an object used for reading data when a device, such as a tag, is within the magnetic induction field.

An {{NDEFReader}} object has the following internal slots:

Internal Slot Initial value Description (non-normative)
[[\Id]] An empty string. The {{NDEFScanOptions}}.id value.
[[\RecordType]] `undefined` The {{NDEFScanOptions}}.recordType value.
[[\MediaType]] An empty string. The {{NDEFScanOptions}}.mediaType value.
[[\Signal]] `undefined` The {{NDEFScanOptions}}.signal to abort the operation.

Note that the internal slots of {{NDEFReader}} come from the |options:NDEFScanOptions| passed to NDEFReader.scan(). Therefore there is maximum one filter associated with any given {{NDEFReader}} object and successive invocations of NDEFReader.scan() with new |options:NDEFScanOptions| will replace existing filters.

The onreading is an {{EventHandler}} which is called to notify that new reading is available.

The onerror is an {{EventHandler}} which is called to notify that an error happened during reading.

NFC state associated with the settings object

The relevant settings object of the active document of a browsing context which supports NFC has an associated NFC state record with the following internal slots:

Internal Slot Initial value Description (non-normative)
[[\Suspended]] `false` A boolean flag indicating whether NFC functionality is suspended or not, initially `false`.
[[\ActivatedReaderList]] empty set A set of {{NDEFReader}} instances.
[[\PendingPush]] empty A <|promise:Promise|, |writer:NDEFWriter|> tuple where |promise| holds a pending {{Promise}} and |writer| holds an {{NDEFWriter}}.

The activated reader objects is the value of the [[\ActivatedReaderList]] internal slot.

The pending push tuple is the value of the [[\PendingPush]] internal slot.

NFC is suspended if the [[\Suspended]] internal slot is `true`.

To suspend NFC, set the [[\Suspended]] internal slot to `true`.

To resume NFC, set the [[\Suspended]] internal slot to `false`.

Internal slots are used only as a notation in this specification, and implementations do not necessarily have to map them to explicit internal properties.

Handling NFC adapters

Implementations MAY use multiple NFC adapters according to the algorithmic steps described in this specification.

Handling visibility change

When the user agent determines that the visibility state of the [=environment settings object / responsible document=] of the current settings object changes, it must run these steps:

  1. Let |document:Document| be the [=environment settings object / responsible document=] of the current settings object.
  2. If |document|'s visibility state is `"visible"`, resume NFC and abort these steps.
  3. Otherwise, suspend NFC and attempt to abort a pending push operation.

The term suspended refers to NFC operations being suspended, which means that no NFC content is pushed by NDEFWriters, and no received NFC content is presented to any {{NDEFReader}} while being suspended.

Aborting pending push operation

To attempt to abort a pending push operation on an environment settings object, perform the following steps:
  1. If there is no pending push tuple |tuple|, abort these steps.
  2. If |tuple|'s writer has already initiated an ongoing NFC data transfer, abort these steps.
  3. Reject |tuple|'s promise with an {{"AbortError"}} {{DOMException}} and abort these steps.

    Rejecting the promise will clear the pending push tuple.

Releasing NFC

To release NFC on an environment settings object, perform the following steps:

  1. Suspend NFC.
  2. Attempt to abort a pending push operation.
  3. Stop the dispatch NFC content steps.
  4. Clear the activated reader objects.
  5. Release the NFC resources associated with |nfc| on the underlying platform.

The UA must release NFC given the document's relevant settings object as additional unloading document cleanup steps.

The NDEFPushOptions dictionary

      dictionary NDEFPushOptions {
        NDEFPushTarget target = "any";
        boolean ignoreRead = true;
        boolean overwrite = true;
        AbortSignal? signal;
      };
    

The target property denotes the intended target for the pending [=NDEFWriter/push()=] operation.

When the value of the ignoreRead property is `true`, the push algorithm will skip invoking the NFC reading algorithm for an NFC tag.

When the value of the overwrite property is `false`, the push algorithm will read the NFC tag regardless of the `ignoreRead` value to determine if it has NDEF records on it, and if yes, it will not execute any pending push.

The signal property allows to abort the [=NDEFWriter/push()=] operation.

The NDEFPushTarget enum

This enum defines the set of intended target values for the [=NDEFWriter/push()=] operation.

      enum NDEFPushTarget {
        "tag",
        "peer",
        "any"
      };
    
tag
The enum value representing the intended target for the [=NDEFWriter/push()=] operation to be a NFC tag.
peer
The enum value representing the intended target for the [=NDEFWriter/push()=] operation to be a NFC peer.
any
The enum value representing the intended target for the [=NDEFWriter/push()=] operation to be a NFC tag or a NFC peer.

The NDEFScanOptions dictionary

To describe which messages an application is interested in, the NDEFScanOptions dictionary is used:

        dictionary NDEFScanOptions {
          USVString id = "";
          USVString recordType;
          USVString mediaType = "";
          AbortSignal? signal;
        };
      

The signal property allows to abort the [=NDEFReader/scan()=] operation.

The id property denotes the URL pattern which is used for matching the record identifier of individual NDEF Records which are being read. The default value `""` means that no matching is performed.

The recordType property denotes the string value which is used for matching the record type of each NDEFRecord object in an NDEF message. If the dictionary member is [= dictionary member/not present =], then it will be ignored by the NFC listen algorithm.

The mediaType property denotes the match pattern which is used for matching the [=NDEFRecord/mediaType=] property of each NDEFRecord object in an NDEF message. The default value `""` means that no matching is performed.

        const options = {
          id: "https://www.w3.org/*",  // any path from the domain is accepted
          mediaType: "application/*json"  // any JSON-based MIME type
        }
      
        const options = {
          id: "https://w3.org/info/restaurant/daily-menu/",
          mediaType: "application/octet-stream"
        }
      

Writing or pushing content

This section describes how to write an NDEF message to an NFC tag or how to push it to an NFC peer device when it is next time in proximity range before a timer expires. At any time there is a maximum of two NDEF messages that can be set for pushing for an origin: one targeted to NFC tags and one to NFC peers, until the current message is sent or the push is aborted.

The push() method

The NDEFWriter.push method, when invoked, MUST run the push a message algorithm:
  1. Let |p:Promise| be a new {{Promise}} object.
  2. Let |message:NDEFMessageSource| be the first argument.
  3. Let |options:NDEFPushOptions| be the second argument.
  4. If there is no underlying NFC Adapter, or if a connection cannot be established, then reject |p| with a {{"NotSupportedError"}} {{DOMException}} and return |p|.
  5. If the UA is not allowed to access the underlying NFC Adapter (e.g. a user preference), then reject |p| with a {{"NotReadableError"}} {{DOMException}} and return |p|.
  6. If pushing data is not supported by the underlying NFC Adapter, then reject |p| with a {{"NotSupportedError"}} {{DOMException}} and return |p|.
  7. If the algorithm is not triggered by user activation, then reject |p| with a {{"NotAllowedError"}} {{DOMException}} and return |p|.
  8. Let |signal:AbortSignal| be the |options|’ dictionary member of the same name if present, or `null` otherwise.
  9. If |signal|’s [= AbortSignal/aborted flag =] is set, then reject |p| with an {{"AbortError"}} {{DOMException}} and return |p|.
  10. If |signal| is not `null`, then add the following abort steps to |signal|:
    1. Run the abort a pending push operation on the environment settings object.
  11. [=promise/React=] to |p|:
    1. If |p| was settled (fulfilled or rejected), then clear the pending push tuple if it exists.
  12. Return |p| and run the following steps in parallel:
    1. An implementation MAY reject |p| with a {{"NotSupportedError"}} {{DOMException}} and abort these steps.

      The UA might abort message push at this point. The reasons for termination are implementation details. For example, the user could have has set a preference to allow a given origin only to read, write, or push data to peers. Also, the implementation might be unable to support the requested operation.

    2. Let |output| be the notation for the NDEF message to be created by UA, as the result of passing |message| to create NDEF message. If this throws an exception, reject |p| with that exception and abort these steps.
    3. Attempt to abort a pending push operation.

      A push replaces all previously configured push operations.

    4. Set `this`.[[\PushOptions]] to |options|.
    5. Set `this`.[[\PushMessage]] to |output|.
    6. Set pending push tuple to (`this`, |p|).
    7. Run the start the NFC push steps whenever an NFC device |device| comes within communication range.

      If NFC is suspended, continue waiting until promise is aborted by the user or an NFC device comes within communication range.

To start the NFC push, run these steps:
  1. Let |p:Promise| be the pending push tuple's promise.
  2. Let |writer| be the pending push tuple's writer.
  3. Let |options:NDEFPushOptions| be |writer|.[[\PushOptions]].
  4. Let |target:NDEFPushTarget| be |options|' target.
  5. If the NFC device in proximity range does not expose NDEF technology for formatting or writing, then reject |p| with a {{"NotSupportedError"}} {{DOMException}} and return |p|.
  6. Verify the following conditions:
  7. In case of success, run the following steps:
    1. If |device| is an NFC tag,
      • If |options|'s ignoreRead is not equal to `true`, run the NFC reading algorithm.
      • Otherwise, if |options|'s overwrite is `false`, read the tag to check whether there are NDEF records on the tag. If yes, then reject |p| with a {{"NotAllowedError"}} {{DOMException}} and return |p|.
      • Let |idHost| be the record identifier host of the tag.
      • If |idHost| is `null`, or different than the serialized host of the current settings object's origin, and the obtain push permission steps return `false`, then reject |p| with {{"NotAllowedError"}} {{DOMException}} and abort these steps.
    2. Let |output:NDEFMessage| be |writer|.[[\PushMessage]].
    3. Initiate data transfer to |device| using |output| as buffer, using the NFC adapter in communication range with |device|.

      If the NFC device in proximity range is an unformatted NFC tag that is NDEF-formatable, format it and write |output| as buffer.

      Multiple adapters should be used sequentially by users. There is very little likelihood that a simultaneous tap will happen on two or multiple different and connected NFC adapters. If it happens, the user will likely need to repeat the taps until success, preferably one device at a time. The error here gives an indication that the operation needs to be repeated. Otherwise the user may think the operation succeeded on all connected NFC adapters.

    4. If the transfer fails, reject |p| with {{"NetworkError"}} {{DOMException}} and abort these steps.
    5. When the transfer has completed, resolve |p|.

Obtaining push permission

To obtain push permission, run these steps:
  1. If there is a prearranged trust relationship, return `true`.
  2. Run the query a permission steps for the Web NFC permission name until completion.
    1. If it resolved with {{PermissionState["granted"]}} (i.e. an expressed permission has been granted to the origin and global object using the [[[PERMISSIONS]]] API), return `true`.
    2. Otherwise, if it resolved with {{PermissionState["prompt"]}}, then optionally request permission from the user for the Web NFC permission name. If that is granted, return `true`.

      The request permission steps are not yet clearly defined. At this point the UA asks the user about the policy to be used with the Web NFC permission name for the given origin and global object, if the user grants permission, return `true`.

  3. Return `false`.

Creating content

Creating NDEF message

To create NDEF message given a |message:NDEFMessageSource| run these steps:

  1. If the |message:NDEFMessageSource| parameter is not of type defined by the NDEFMessageSource union, throw a {{TypeError}} and abort these steps.
  2. If the |message:NDEFMessageSource| parameter is of NDEFMessageInit type, and |message|'s records [= list/is empty =], throw a {{TypeError}} and abort these steps.
  3. Let |output| be the notation for the NDEF message to be created by the UA as a result of these steps.
  4. [= list/For each =] |record:NDEFRecordInit| in the list |message|'s records, run the following steps, or make sure that the underlying platform provides equivalent values to |ndef|:
    1. If |record|'s recordType is `undefined`:
      1. If |record|'s data is `undefined`, reject |promise| a {{TypeError}} and abort these steps.
      2. Otherwise, if the type of |record|'s data is {{DOMString}}, then set |record|'s recordType to "`text`".
      3. Otherwise, set |record|'s recordType to "`mime`".
    2. Let |ndef| be the result of passing |record| to the algorithm below switching on |record|'s recordType. If the algorithm throws an exception |e|, reject |promise| with |e| and abort these steps.
      "`empty`"
      "`text`"
      "`url`"
      "`mime`"
      external type name
      local type name
        If |record|'s recordType is a local type and if |record| is a payload to another NDEF record,
      • If |record|'s data is of type {{NDEFMessageInit}}, then return the result of running the create NDEF message given |record|'s data.
      • Otherwise, map local type to NDEF given |record|.
      unmatched type
      • [= exception/throw =] a {{TypeError}}
    3. If |message| is of type {{NDEFMessageInit}} and its |id:string| is not `null`:
    4. Add |ndef| to |output|.

Mapping empty record to NDEF

To map empty record to NDEF given a |record:NDEFRecordInit|, run these steps:
  1. If |record|'s |mediaType| is not `undefined`, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. Let |ndef| be the notation for the NDEF record to be created by the UA.
  3. Set the |ndef|'s TNF field to `0` (empty record).
  4. Set the |ndef|'s IL field to `0`.
  5. Set |ndef|'s TYPE LENGTH field, and PAYLOAD LENGTH field to `0`, and omit TYPE field and PAYLOAD field.
  6. Return |ndef|.

Mapping string to NDEF

To map text to NDEF given a |record:NDEFRecordInit|, run these steps:

This is useful when clients specifically want to write text in a [=well-known type record=]. Other options would be to use the value "`mime`" with an explicit MIME type text type, which allows for better differentiation, e.g. when using "`text/xml`", or "`text/vcard`".

  1. If |record|'s |mediaType| is not `undefined`, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. If the type of |record|'s data is not a {{DOMString}} or a {{BufferSource}}, [= exception/throw =] a {{TypeError}} and abort these steps.
  3. Let |documentLanguage:string| be the [=document element=]'s lang attribute.
  4. If |documentLanguage| is the empty string, set it to "`en`".
  5. Let |language:string| be |record|'s lang if it [= map/exists =], or else to |documentLanguage|.
  6. Let |encoding label:string| be |record|'s encoding if it [= map/exists =], or "`utf-8`".
  7. If |encoding label| is not equal to "`utf-8`", "`utf-16`", "`utf-16le`" or "`utf-16be`" [= exception/throw =] a {{TypeError}}.
  8. Let |encoding name| be the [=encoding/name|name=] obtained from |encoding label|.
  9. Let |header:byte| be a byte constructed the following way:
    1. If |encoding name| is equal to UTF-8, set bit `7` to the value `0`, or else set the value to `1`.
    2. Set bit `6` to the value `0` (reserved).
    3. Let |languageLength:octed| be the length of the |language| string.
    4. If |languageLength| cannot be stored in 6 bit (|languageLength| > 63), [= exception/throw =] a {{SyntaxError}}.
    5. Set bit `5` to bit `0` to |languageLength|.
  10. Let |data:byte sequence| be an empty [= byte sequence =].
    1. Set the first byte (position 0) of |data| to |header|.
    2. Set position 1 (second byte) to position |languageLength| of |data| to |language|.
    3. Switch on the type of |record|'s data:
      {{DOMString}}
      1. Let |stream:byte stream| be the resulting byte stream of running UTF-8 encode on |record|'s data.
      2. Read bytes from |stream| into |data| (from position |languageLength| + 1) until read returns end-of-stream.
      {{BufferSource}}
      1. Set bytes from |record|'s data into |data| (from position |languageLength| + 1) .
  11. Set |length:unsigned long| to the [=byte sequence/length=] of |data|.
  12. Let |ndefRecord| be the notation for the NDEF record to be created by the UA.
    1. Set the |ndefRecord|'s TNF field to `1` ( [=well-known type record=]).
    2. Set the |ndefRecord|'s TYPE field to "`T`" (`0x54`).
    3. Set the |ndefRecord|'s PAYLOAD LENGTH field to |length|.
    4. If |length| > `0`, set the |ndefRecord|'s PAYLOAD field to |data|.
  13. Return |ndefRecord|.

Mapping URL to NDEF

To map a URL to NDEF given a |record:NDEFRecordInit|, run these steps:
  1. If |record|'s |mediaType| is not `undefined`, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. If |record|'s data is not a {{DOMString}}, [= exception/throw =] a {{TypeError}} and abort these steps.
  3. Let |url:URL| be the result of parsing |record|'s data.
  4. If |url| is failure, [= exception/throw =] a {{TypeError}} and abort these steps.
  5. Let |serializedURL:string| be serialization of |url|.
  6. Match the URI prefixes as defined in [[[NFC-STANDARDS]]], URI Record Type Definition specification, Section 3.2.2, against the |serializedURL|.
  7. Let |prefixString:string| be the matched prefix or else the empty string.
  8. Let |prefixByte:byte| be the corresponding prefix number, or else `0`.
  9. Let |shortenedURL:string| be |serializedURL| with |prefixString| removed from the start of the string.
  10. Let |data:byte sequence| be an empty [= byte sequence =].
    1. Set the first byte of |data| to |prefixByte|.
    2. Let |stream:byte stream| be the resulting byte stream of running UTF-8 encode on |shortenedURL|.
    3. Read bytes from |stream| into |data| (from position 1) until read returns end-of-stream.
  11. Set |length:unsigned long| to the [=byte sequence/length=] of |data|.
  12. Let |ndefRecord| be the notation for the NDEF record to be created by the UA.
    1. Set the |ndefRecord|'s TNF field to `1` ([=well-known type record=]).
    2. Set the |ndefRecord|'s TYPE field to "`U`" (`0x55`).
    3. Set the |ndefRecord|'s PAYLOAD LENGTH field to |length|.
    4. If |length| > `0`, set the |ndefRecord|'s PAYLOAD field to |data|.
  13. Return |ndefRecord|.

Mapping binary data to NDEF

To map binary data to NDEF given a |record:NDEFRecordInit|, run these steps:
  1. If the type of a |record|'s data is not a {{BufferSource}}, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. Let |mimeTypeRecord| be the MIME type returned by running parse a MIME type on |record|'s mediaType.
    1. If |mimeTypeRecord| is failure, let |mimeTypeRecord| be a new MIME type record whose type is "`application`", and subtype is "`octet-stream`".
  3. Set |arrayBuffer| to |record|'s data.
  4. Set |length:unsigned long| to |arrayBuffer|.[[\ArrayBufferByteLength]].
  5. Set |data:byte sequence| to |arrayBuffer|.[[\ArrayBufferData]].
  6. Let |ndefRecord| be the notation for the NDEF record to be created by the UA.
    1. Set the |ndefRecord|'s TNF field to `2` (MIME type).
    2. Set the |ndefRecord|'s TYPE field to the result of serialize a MIME type with |mimeTypeRecord| as the input.
    3. Set the |ndefRecord|'s PAYLOAD LENGTH field to |length|.
    4. If |length| > `0`, set the |ndefRecord|'s PAYLOAD field to |data|.
  7. Return |ndefRecord|.

Mapping external data to NDEF

To map external data to NDEF given a |record:NDEFRecordInit|, run these steps:
  1. If |record|'s |mediaType| is not `undefined`, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. If the type of a |record|'s data is not a {{BufferSource}}, [= exception/throw =] a {{TypeError}} and abort these steps.
  3. Set |arrayBuffer| to |record|'s data.
  4. Set |length:unsigned long| to |arrayBuffer|.[[\ArrayBufferByteLength]].
  5. Set |data:byte sequence| to |arrayBuffer|.[[\ArrayBufferData]].
  6. Let |ndefRecord| be the notation for the NDEF record to be created by the UA.
    1. Set |ndefRecord|'s TNF field to `4` (external type record).
    2. Set the |ndefRecord|'s TYPE field to |record|'s recordType.
    3. Set the |ndefRecord|'s PAYLOAD LENGTH field to |length|.
    4. If |length| > `0`, set the |ndefRecord|'s PAYLOAD field to |data|.
  7. Return |ndefRecord|.

Mapping local type to NDEF

To map local type to NDEF given a |record:NDEFRecordInit|, run these steps:
  1. If |record|'s |mediaType| is not `undefined`, [= exception/throw =] a {{TypeError}} and abort these steps.
  2. If the type of a |record|'s data is not a {{BufferSource}}, [= exception/throw =] a {{TypeError}} and abort these steps.
  3. Set |arrayBuffer| to |record|'s data.
  4. Set |length:unsigned long| to |arrayBuffer|.[[\ArrayBufferByteLength]].
  5. Set |data:byte sequence| to |arrayBuffer|.[[\ArrayBufferData]].
  6. Let |ndefRecord| be the notation for the NDEF record to be created by the UA.
    1. Set the |ndefRecord|'s TYPE field to |record|'s recordType.
    2. If |record|'s recordType is a standard local type to smart poster or a handover record, then set |ndefRecord|'s TNF field to `1` ([=well-known type record=]), otherwise set |ndefRecord|'s TNF field to `4` (external type record).
    3. Set the |ndefRecord|'s PAYLOAD LENGTH field to |length|.
    4. If |length| > `0`, set the |ndefRecord|'s PAYLOAD field to |data|.
  7. Return |ndefRecord|.

Creating a record identifier

To create a record identifier given URL string |id:string|, run these steps:
  1. Let |origin| be the current settings object's origin.
  2. Let |host| be |origin|'s host, serialized.
  3. Let |urlRecord| be the result of parsing |id| with "`https://`" + |host| as the base URL.
  4. If any of the following cases matches, [= exception/throw =] a {{TypeError}} and abort these steps:
    • If |urlRecord| is failure.
    • If |urlRecord|'s protocol is not equal to `"https:`".
    • If |urlRecord|'s host doesn't end with |host|'s [=host/registrable domain=].
  5. Let |identifier| be |urlRecord|, serialized.
  6. Return |identifier|.

Listening for content

If there are any {{NDEFReader}} instances in activated reader objects then the UA MUST listen to NDEF messages.

To listen for NFC content, the client MUST activate an {{NDEFReader}} instance by calling NDEFReader.scan(). When attaching an event listener for the "`reading`" event on it, NFC content is accessible to the client.

Each {{NDEFReader}} can accept NDEF messages based on data type, and record identifier (URL) filters.

Filtering by a record identifier URL pattern, means that it will be matched against the URLs found in the NDEF records' ID field, thus the presence of such is required.

Match patterns

A match pattern is defined by the following ABNF:
          match-pattern  = top-level-type "/" [ tree "." ] subtype [ "+" suffix ] [ ";" parameters ]
          top-level-type = "*" / < VCHAR except "/" and "*" >
          subtype        = "*" / < VCHAR except "+" >
        
A match pattern is a glob used for matching MIME types, for instance the pattern "`application/*+json`" matches "`application/calendar+json`", but does not match "`application/json`". The pattern "`*/*json`", on the other hand, matches both.

URL patterns

A URL pattern is a URL record that can be used to match the optional record identifier of every NDEF record ID field. A valid URL pattern is a valid URL record whose [= url/scheme =] component is equal to "`https`".
A URL pattern's [= url/scheme =], [= url/host =] and [= url/path =] components that are used by the URL pattern match algorithm have the following matching rules:
URL pattern component Matching rule for record identifier
[= url/host =] exact match or ends with (URL pattern's [= url/host =] prepended with "`.`").
[= url/path =] If URL pattern's path is "`/*`", match any record identifier path. Otherwise, begins with URL pattern's [= url/path =].

For example, '`https://mydomain.com/*`' will match '`https://service.mydomain.com/myapp/`' and '`https://info.mydomain.com/general/`', while '`https://app.mydomain.com/contacts`' will match '`https://app.mydomain.com/contacts`' and '`https://app.mydomain.com/contacts/all`' The '`*`' is a valid character for the URL path component, therefore, '`https://www.mydomain.com/*`' pattern will match both '`https://www.mydomain.com/*`' and '`https://www.mydomain.com/service`' URLs.

URL pattern match algorithm

To match record identifier with URL pattern for a given record identifier and URL pattern, run these steps:
  1. Let |raw id| be a record identifier passed to this algorithm.
  2. Let |raw pattern| be a URL pattern passed to this algorithm.
  3. If |raw id| and |raw pattern| are empty strings, return `true`.
  4. Let |id| be the result of running the basic URL parser on |raw id|.
  5. If |id| is failure, return `false`.
  6. Let |pattern| be the result of running the basic URL parser on |raw pattern|.
  7. If |pattern| is failure, return `false`.
  8. Let |subdomain pattern| be the result of prepending "`.`" to |pattern|'s [= url/host =].
  9. If |id|'s [= url/host =] does not end with |subdomain pattern| and |id|'s [= url/host =] is not equal to |pattern|'s [= url/host =], return `false`.
  10. If |pattern|'s [= url/path =] is equal to "`/*`", return `true`.
  11. If |id|'s [= url/path =] begins with |pattern|'s [= url/path =], return `true`.
  12. Otherwise, return `false`.

The scan() method

Incoming NFC content is matched using {{NDEFReader}} instances.

When the NDEFReader.scan method is invoked, the UA MUST run the following NFC listen algorithm:
  1. Let |p:Promise| be a new {{Promise}} object.
  2. Let |reader:NDEFReader| be the {{NDEFReader}} instance.
  3. Let |options| be first argument.
  4. [= list/For each =] |key| → |value| of |options|:
    1. If |key| equals "`signal`" and |value| is not `undefined`, set |reader|.[[\Signal]] to |value|.
    2. Otherwise, if |key| equals "`id`", set |reader|.[[\Id]] to |value|, prepended with "`https://`".
    3. Otherwise, if |key| equals "`recordType`", set |reader|.[[\RecordType]] to |value|.
    4. Otherwise, if |key| equals "`mediaType`", set |reader|.[[\MediaType]] to |value|.
  5. If there is no underlying NFC Adapter, or if a connection cannot be established, then reject |p| with a {{"NotSupportedError"}} {{DOMException}} and return |p|.
  6. If the UA is not allowed to access the underlying NFC Adapter (e.g. a user preference), then reject |p| with a {{"NotReadableError"}} {{DOMException}} and return |p|.
  7. If the algorithm is not triggered by user activation, then reject |p| with a {{"NotAllowedError"}} {{DOMException}} and return |p|.
  8. If |reader|.[[\Signal]]'s [= AbortSignal/aborted flag =] is set, then reject |p| with a {{"AbortError"}} {{DOMException}} and return |p|.
  9. If |reader|.[[\Signal]] is not `null`, then add the following abort steps to |reader|.[[\Signal]]:
    1. Remove the {{NDEFReader}} instance from the activated reader objects.
    2. If the activated reader objects [= list/is empty =], then make a request to stop listening to NDEF messages on all NFC adapters.
  10. Run the following steps in parallel:
    1. If the obtain reading permission steps return `false`, then reject |p| with a {{"NotAllowedError"}} {{DOMException}} and return |p|.
    2. If this is the first listener being set up, then make a request to all NFC adapters to listen to NDEF messages.
    3. If the request fails, then the UA MAY reject |p| with a {{"NotSupportedError"}} {{DOMException}} and return |p|.
    4. If the |reader|.[[\Id]] is not an empty string and it is not a valid URL pattern, then reject |p| with a {{"SyntaxError"}} {{DOMException}} and return |p|.
    5. Add |reader| to the activated reader objects.
    6. If the {{Document}} of the top-level browsing context is not visible (e.g. the user navigated to another page), then the registered activated reader objects still SHOULD continue to exist, but SHOULD become paused, i.e. the UA SHOULD NOT check and use them until the {{Document}} is visible again.
    7. Whenever the UA detects NFC technology, run the NFC reading algorithm.
To obtain reading permission, run these steps:
  1. If there is a prearranged trust relationship, return `true`.
  2. Otherwise, if the user has earlier denied permission for the calling origin for all future calls of scan() as well, then return `false`.
  3. Otherwise, UAs SHOULD ask for forgiveness with relevant information displayed to the user.

    The ask for forgiveness interaction might show choices like "block now" or "block forever", etc. If the user has chosen to "block forever" the given origin, it is the responsibility of the UA to remember these user choices for each origin, regardless of which NFC adapter is used, and consult them on later invocations.

    In this step UAs are advised to notify users about that reading NFC content may indirectly reveal the physical location of the user.

  4. Return `true`.

The NFC reading algorithm

To receive NDEF content, run the NFC reading algorithm:
  1. If NFC is suspended, abort these steps.
  2. If the NFC device in proximity range does not expose NDEF technology for reading or formatting, run the following sub-steps:
    1. [= list/For each =] {{NDEFReader}} instance |reader:NDEFReader| in the activated reader objects, run the following sub-steps:
      1. Fire an event named "`error`" at |reader|.
    2. Abort these steps.
  3. Let |serialNumber:serialNumber| be the device identifier as a series of numbers, or `null` if unavailable.
  4. If |serialNumber| is not `null`, set it to the string of U+003A (`:`) concatenating each number represented as ASCII hex digit, in the same order.
  5. Let |message:NDEFMessage| be a new NDEFMessage object, with |message|'s records set to the empty list.
  6. If the NFC device in proximity range is an unformatted NFC tag that is NDEF-formattable, let |input| be `null`. Otherwise, let |input| be the notation for the NDEF message which has been received.

    The UA SHOULD represent an unformatted NFC tag as an NDEF message containing no NDEF records, i.e. an empty array for its [=NDEFMessage/records=] property.

  7. [= list/For each =] NDEF record which is part of |input|, run the following sub-steps:
    1. Let |ndef| be the notation for the current NDEF record with |typeNameField:number| corresponding to the TNF field and |payload:byte sequence| corresponding to the PAYLOAD field data.
    2. Let |record:NDEFRecord| be the result of parse an NDEF record on |ndef|.
    3. If |record| is not `null`, append |record| to |message|'s records.
  8. If NFC is not suspended, run the dispatch NFC content steps with given |serialNumber| and |message|.

Dispatching NFC content

To dispatch NFC content given a |serialNumber:serialNumber| of type serialNumber, |message:NDEFMessage| of type NDEFMessage, run these steps:

  1. [= list/For each =] {{NDEFReader}} instance |reader:NDEFReader| in the activated reader objects, run the following sub-steps:
    1. If |reader|.[[\Id]] is [= dictionary member/present =] and doesn't match any |record:NDEFRecord|'s id where |record| is an element of |message|, [= iteration/continue =].
    2. If |reader|.[[\RecordType]] is [= dictionary member/present =] and it is not equal to any |record|'s recordType where |record| is an element of |message|, [= iteration/continue =].
    3. If |reader|.[[\MediaType]] is not `""` and it is not equal to any |record|'s mediaType where |record| is an element of |message|, [= iteration/continue =].
    4. Fire an event named "`reading`" at |reader| using NDEFReadingEvent with its serialNumber attribute initialized to |serialNumber| and message attribute initialized to |message|.

Parsing content

Parsing records from bytes

To parse records from bytes given |bytes:byte sequence|, run these steps:
  1. Let |records| be the empty list.
  2. As long as there are unread bytes of |bytes|, run the following sub-steps:
    1. If the remaining length of |bytes| is less than `3`, abort these sub-steps.
    2. If any of the following steps requires reading bytes beyond the remaining length of |bytes|, return |records|.
    3. Let |ndef| be the notation for the current NDEF record
    4. Let |header:byte| be the next byte of |bytes|.
      1. Let |messageBegin:boolean| (MB field) be the left most bit (bit 7) of |header|.
      2. If this is the first iteration of these sub-steps and |messageBegin| is `false`, return |records|.
      3. Let |messageEnd:boolean| (ME field) be bit 6 of |header|.
      4. As chunked records are not allowed as sub records, ignore bit 5 (CF field) is ignored.

      5. Let |shortRecord:boolean| (SR field) be bit 4 of |header|.
      6. Let |hasIdLength:boolean| (IL field) be bit 3 of |header|.
      7. Let |ndef|'s |typeNameField:number| (TNF field) be the integer value of bit 2-0 of |header|.
    5. Let |typeLength:number| be the integer value of next byte (TYPE LENGTH field) of |bytes|.
    6. If |shortRecord| is `true`, let |payloadLength:number| be the integer value of next byte (PAYLOAD LENGTH field) of |bytes|.
    7. Otherwise, let |payloadLength| be the integer value of the next 4 bytes of |bytes|.
    8. If |hasIdLength| is `true`, let |idLength:number| be the integer value of next byte (ID LENGTH field) of |bytes|, otherwise let it be `0`.
    9. If |typeLength| > 0, let |ndef|'s |type:string| be result of running UTF-8 decode on the next |typeLength| (TYPE field) bytes, orelse let |type| be the empty string.
    10. If |idLength| > 0, let |ndef|'s |id:string| be result of running UTF-8 decode on the next |idLength| (ID field) bytes, orelse let |ndef|'s |id| be the empty string.
    11. Let |ndef|'s |payload| be the byte sequence of the last |payloadLength| (PAYLOAD field) bytes, which may be `0` bytes.
    12. Let |record:NDEFRecord| be the result of parse an NDEF record on |ndef|.
    13. If |record| is not `null`, append |record| to |records|.
    14. If |messageEnd| is `true`, abort these sub-steps.
  3. Return |records|.

Parsing NDEF records

To parse an NDEF record given |ndef| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s id to |ndef|'s |id:string|.
  2. Set |record|'s lang to `null`.
  3. Set |record|'s encoding to `null`.
  4. If |ndef|'s |typeNameField:number| is `0` (empty record), then set |record|'s recordType to "`empty`".
  5. If |ndef|'s |typeNameField| is `1` ([=well-known type record=]):
    1. Set |record| to the result of the algorithm below switching on |ndef|'s |type:string|:
      "`T`" (`0x54`)
      "`U`" (`0x55`)
      "`Sp`" (`0x53` `0x70`)
  6. If |ndef|'s |typeNameField| is `2` (MIME type record), then set |record| to the result of running parse an NDEF MIME type record on |ndef|, or make sure that the underlying platform provides equivalent values to the |record| object's properties.
  7. Otherwise, if |ndef|'s |typeNameField| is `3` (absolute-URL record), then set |record| to the result of running parse an NDEF absolute-URL record on |ndef|.
  8. Otherwise, if |ndef|'s |typeNameField| is `4` (external type record), then set |record| to the result of running parse an NDEF external type record on |ndef|, or make sure that the underlying platform provides equivalent values to the |record| object's properties.
  9. Otherwise, if |ndef|'s |typeNameField| is `5` (unknown record) then set |record| to the result of running parse an NDEF unknown record on |ndef|, or make sure that the underlying platform provides equivalent values to the |record| object's properties.

Parsing NDEF well-known `T` records

To parse an NDEF text record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`text`".
  2. Let |mimeType| be a MIME type with type "`text`", subtype "`plain`" and parameters equal to an empty ordered map.
  3. If |ndefRecord|'s PAYLOAD field is not present, set |record|.[[\PayloadData]] to `undefined` and return |record|.
  4. Let |header:byte| be the first byte of |ndefRecord|'s PAYLOAD field.
  5. Let |languageLength:octet| be the value given by bit `5` to bit `0` of the |header|.
  6. Let |language:string| be the result of running ASCII decode on second byte to the |languageLength| + `1` byte, inclusive.
  7. Set |record|'s lang to |language|.
  8. Set |record|'s encoding be "`utf-8`" if bit `7` ([=MB field=]) of |header| is equal to the value `0`, or else "`utf-16be`".
  9. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field.
  10. Set |record|.[[\PayloadData]] to |buffer|.
  11. return |record|.

Parsing NDEF well-known `U` records

To parse an NDEF URL record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`url`".
  2. If |ndefRecord|'s PAYLOAD field is not present, set |record|.[[\PayloadData]] to `undefined` and return |record|.
  3. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field.
  4. Let |prefixByte:byte| be the value of the first byte of |buffer|.
  5. If the value of |prefixByte| matches the URL expansion codes in the [[[NFC-STANDARDS]]] URI Record Type Definition specification, Section 3.2.2, Table 3, then
    1. Let |prefixString:string| be the byte sequence value corresponding to the value of |prefixByte|.
    2. Set |record|.[[\PayloadData]] to |prefixString| appended to |buffer|.
  6. Otherwise, if there is no match for |prefixByte|, set |record|.[[\PayloadData]] to |buffer|.
  7. return |record|.

Parsing NDEF well-known `Sp` records

To parse an NDEF smart-poster record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`smart-poster`".
  2. If |ndefRecord|'s PAYLOAD field is not present, set |record|.[[\PayloadData]] to `undefined` and return |record|.
  3. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field.
  4. Set |record|.[[\PayloadData]] to |buffer|.
  5. return |record|.

Parsing NDEF MIME type records

To parse an NDEF MIME type record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`mime`".
  2. Set |record|'s mediaType to the result of serialize a MIME type with |mimeType| as the input.
  3. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field if that exists, or otherwise `undefined`.
  4. Set |record|.[[\PayloadData]] to |buffer|.
  5. return |record|.

Parsing NDEF absolute-URL records

To parse an NDEF absolute-URL record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`absolute-url`".
  2. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s TYPE field.
  3. Set |record|.[[\PayloadData]] to |buffer|.
  4. return |record|.

Parsing NDEF external type records

To parse an NDEF external type record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to the value of |ndefRecord|'s TYPE field.
  2. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field if that exists, or otherwise `undefined`.
  3. Set |record|.[[\PayloadData]] to |buffer|.
  4. return |record|.

Parsing NDEF unknown type records

To parse an NDEF unknown record given a |ndefRecord| into a |record:NDEFRecord|, run these steps:
  1. Set |record|'s recordType to "`unknown`".
  2. Let |buffer:byte sequence| be the byte sequence of |ndefRecords|'s PAYLOAD field if that exists, or otherwise `undefined`.
  3. Set |record|.[[\PayloadData]] to |buffer|.
  4. return |record|.

Security and Privacy

The trust model, attacker model, threat model and possible mitigation proposals for Web NFC are presented in the Security and Privacy document. This section presents the chosen security and privacy model through normative requirements to implementations.

Chain of trust

Web pages using Web NFC are not trusted. This means that the user needs to be aware of exactly what a web page is intending to do with NFC at any given moment. Implementations need to make sure that when the user authorizes a method of this API, then only that action is run, without side effects, and exactly in the context and the number of times the user allows the execution of NFC related operations, according to the algorithmic steps detailed in this specification.

The integrity of NFC content SHOULD NOT be trusted when used for implementing security policies, for instance the authenticity of record identifier, unless a prearranged trust relationship exists.

Security considerations for media types in general are discussed in [[RFC2048]] and [[RFC2046]].

Threats

The main threats are summarized in the Security and Privacy document.

In this specification the following threats are handled with the highest priority:
  • User data privacy: involuntary sharing of user data (such as location, contacts, personal data, etc) from an NFC-capable device such as a phone, tablet, or PC.
  • Protecting existing NFC tags from being overwritten by malicious web pages.

Permissions and user prompts

This specification attempts to minimize user prompting and uses implicit security policies to address the threats. However, this specification does not describe, nor does it mandate specific user prompting policies. The term obtain permission is used for acquiring trust for a given operation.

The [[[PERMISSIONS]]] API is suggested to be used by UAs for implementing NFC related [[[PERMISSIONS]]] in order to minimize the need for user prompting.

All expressed permissions that are preserved beyond the current browsing session MUST be revocable.

Security policies

This section summarizes the security policies which are specified as normative requirements in the respective algorithms of this specification.

Secure Context

Only secure contexts are allowed to access NFC content. Browsers may ignore this rule for development purposes only.

Visible document

Web NFC functionality is allowed only for the {{Document}} of the top-level browsing context, which must be visible.

This also means that UAs should block access to the NFC radio if the display is off or the device is locked. For backgrounded web pages, receiving and pushing NFC content must be suspended.

Permissions controls

Making an NFC tag read-only must obtain permission, or otherwise fail.

Setting up listeners for reading NFC content should obtain permission.

The process of reading an NDEF message does not need to obtain permission.

Pushing NFC content to an NFC peer does not need to obtain permission, but the other rules in this section apply. See the [[[#writing-or-pushing-content]]] section.

Pushing an NDEF message to an NFC tag does not need to obtain permission, if the existing NDEF message only contains NDEF records without ID fields, or with ID fields matching the [=host/registrable domain=] of the current settings object's origin. Otherwise the UA must obtain permission for pushing NFC content which overwrites existing information. See also the [[[#writing-or-pushing-content]]] section.

Since all local content that a web page has access to can be shared with NFC, the user needs to be clearly aware about the permissions granted to the web page using Web NFC.

Store site URL as record identifier when writing data

When pushing an NDEF message, the [= host/registrable domain =], serialized, of the current settings object must be stored as the record identifier in each top-level NDEF Record.

Warn risk of physical location leak

When listening for and pushing NFC content, the UA may warn the user that the given origin may be able to infer physical location.

Restrict automatic handling

The payload data on NFC content is untrusted, and must not be used by the UA to do automatic handling such as opening a web page with a URL found in an NFC tag, unless the user approves that.

Acknowledgments

The editors would like to thank Jeffrey Yasskin, Anne van Kesteren, Anssi Kostiainen, Domenic Denicola, Daniel Ehrenberg, Jonas Sicking, Don Coleman, Salvatore Iovene, Rijubrata Bhaumik, and Wanming Lin for their contributions to this document.

Special thanks to Luc Yriarte and Samuel Ortiz for their initial work on exposing NFC to the web platform, and for their support for the current approach.