Skip to main content
Fifteen minutes, one physical phone, and one cheap NTAG213 sticker. By the end you will have a screen that reads a tag, writes one, and handles cancellation without showing the user an error that is not one.
You need a physical device. Neither the iOS Simulator nor an Android emulator has an NFC controller, so there is nothing for the library to talk to.

1. Install and build

With Expo, add the plugin before prebuilding:
app.json
Full detail, including the requirements and the Apple Developer portal step, is in Installation.

2. Check the device before offering the feature

enabled is a real answer on both platforms here, not a hardcoded true on iOS. On Android the user can switch NFC off while your screen is open, which is why the React binding below subscribes rather than reading once.

3. Read a tag

Three things are happening that are worth naming, because they are the whole design:
The session only surfaces tags carrying one of the technologies you asked for. On iOS it also decides which reader-session polling options are used.
Before you narrow, a Tag has an id, a technology list and platform facets — and no technology methods at all. tag.readNdef() on an un-narrowed tag is a compile error, not a runtime surprise.
withTag closes on every path out: returning, throwing, an AbortSignal firing, the timeoutMs elapsing, or the platform ending the session underneath. There is no finally for you to forget.

4. Read the records you got

readNdef gives you decoded records, not bytes. Pair each decoder with its guard:
Calling a decoder on the wrong record type throws invalidArgument rather than returning garbage, so the guard is not optional politeness — it is how you avoid the throw. The NDEF codec covers every record type.

5. Write a tag

Check before you write. A tag that runs out of room part-way through a write is left in a state nobody can read:
Asking for both ndef and ndefFormatable and narrowing on each is the pattern that makes “it works on a used tag and not on a new one” go away.

6. Handle the four errors that are ordinary life

Every rejection is an NfcError with a stable code. Four of them are not bugs and not tag problems — they are what happens on an ordinary Tuesday:
Use NfcError.is() rather than instanceof. It brands on the error’s name, so it keeps working when two copies of the package end up in one bundle — which happens, and which breaks instanceof in a way that is very hard to see.
Every code, its cause and its remedy: the error reference.

7. Put it on screen

The React binding handles three things a hand-written version usually does not: unmounting mid-scan cancels the session, a second scan() joins the first rather than opening a second session, and NFC being switched off is reflected without a refresh.
ScanScreen.tsx
Cancelling settles as idle rather than as an error, because the user asked for it and there is nothing to report to them.
On Android there is no system sheet, so nothing appears while scanning is true. Show your own “hold your tag near the phone” state — the button label above is the minimum version of that.

Where to go next

Sessions

withTag is one of three ways to get a tag. The other two are for kiosks and for your own UI.

Tags

Every technology, what narrowing gives you, and the platform facets.

Smartcards

DESFire and JavaCard, plus the iOS AID declaration without which the tag never arrives.

React hooks

useNfcAvailability, useNfcScan, useNfcTagStream.