NFC tags

Read, write, cancel, and launch from NDEF tags with the device's NFC hardware.

Check availability

Call isAvailable() before showing any NFC UI — it's true only when NFC is both enabled in this app build and usable on this device (hardware present, radio on). You can also check bdk.capabilities.has("nfc") earlier, before device capabilities have loaded.

import { createBdkNative } from "@bdk/native/browser";

const bdk = createBdkNative();

if (await bdk.nfc.isAvailable()) {
  // show the "Scan tag" button
}

Read a tag

Start a one-shot NDEF scan and wait for a physical tag. Reading requires an actual NFC tag near the device — there's no simulated result.

If you call read() again while a scan is already in progress, it resolves with nfc/busy.

const result = await bdk.nfc.read({ timeoutMs: 15000 });

if (!result.ok) {
  console.warn(result.code, result.message); // e.g. nfc/tag_lost, nfc/busy
} else {
  console.log(result.tag.id, result.tag.ndef.records);
}

Write a tag

Write NDEF records to a tag during a scan session. write() replaces the tag's existing NDEF message — it doesn't append. Each record carries a type: { type: "text", text, language }, { type: "uri", uri }, or { type: "external", externalType, payloadBase64 }, up to the number of records this app build allows.

const result = await bdk.nfc.write({
  records: [{ type: "uri", uri: "https://example.com" }]
});

if (result.ok) {
  console.log(result.tag.id, result.bytesWritten);
}

Cancel a scan

Stop an in-progress read or write session. If there's no active session, cancel() still resolves ok: true with cancelled: false — it isn't an error. Narrow on result.ok before reading cancelled.

const result = await bdk.nfc.cancel();

if (result.ok && result.cancelled) {
  // an in-progress session was cancelled
}

Launch the app from a tag

getLaunchTag() tells you whether this launch of the app was triggered by scanning a tag. It's consumed once — call it a single time at startup. For a tag scanned while the app is already open, subscribe to the nfc.launchTag event instead.

On Android 16, a URL-record tag opens the browser instead of launching the app, because of how the OS routes URL NDEF tags. Use a custom-scheme record or an external record if you need launch-by-tag to work reliably.

bdk.on("nfc.launchTag", ({ url, tag }) => {
  console.log("scanned while running", url, tag);
});

const launch = await bdk.nfc.getLaunchTag();

if (launch.ok && launch.launched) {
  console.log("launched from a tag", launch.url, launch.tag);
}