Skip to content
Blog

Reading NFC Tags in Flutter with nfc_manager

Start a session with NfcManager.instance.startSession, parse NDEF records from NfcTag, and configure Android NFC permissions plus iOS reader entitlements.

Published on • November 5, 2026

AI Assistant

Tap-to-read is one of the few hardware features that behaves almost identically on Android and iOS — once you have the platform entitlements in place. nfc_manager wraps both behind a single session-based API.

Installing and configuring nfc_manager

nfc_manager is at 4.2.1 (published April 3, 2026 by the verified okadan.net publisher, MIT licence) and supports Android and iOS only — there is no web, desktop or macOS implementation. On Android you add one manifest entry:

<uses-permission android:name="android.permission.NFC" />

iOS needs more: the Near Field Communication Tag Reader Session Formats entitlement, an NFCReaderUsageDescription string in Info.plist, and com.apple.developer.nfc.readersession.iso7816.select-identifiers entries for the application families you want to read. If you pass NfcPollingOption.iso18092 to startSession, you must also declare com.apple.developer.nfc.readersession.felica.systemcodes or the session errors out.

Starting a discovery session

Availability first, then a session that stays open until you stop it:

final availability = await NfcManager.instance.checkAvailability();
if (availability != NfcAvailability.enabled) {
  // NFC missing or temporarily disabled
  return;
}

NfcManager.instance.startSession(
  pollingOptions: {NfcPollingOption.iso14443}, // also iso18092, iso15693
  onDiscovered: (NfcTag tag) async {
    await handle(tag);
    await NfcManager.instance.stopSession();
  },
);

onDiscovered fires once per tag and receives an NfcTag. The session is a long-lived platform reader session, so decide explicitly when to call stopSession() — typically as soon as you have parsed the payload or when the user leaves the screen.

In practice, pair every startSession() with a stopSession() in the same place — often the widget’s dispose — so the reader is released the moment your flow finishes. Re-checking checkAvailability() each time you open the screen is also cheap insurance: users toggle NFC off in system settings between sessions.

Parsing what the tag carries

An NfcTag is a raw bag of platform data; you convert it with static from factories. For NDEF payloads, the companion nfc_manager_ndef package (1.1.0, depending on nfc_manager ^4.1.0) provides the cross-platform abstraction:

import 'package:nfc_manager_ndef/nfc_manager_ndef.dart';

final Ndef? ndef = Ndef.from(tag);
if (ndef == null) {
  // Not an NDEF-compatible tag
  return;
}

// The message captured when the tag was discovered, or a fresh read:
final NdefMessage? message = ndef.cachedMessage ?? await ndef.read();
if (message == null) return;

for (final NdefRecord record in message.records) {
  print(record.typeNameFormat); // well-known, mime, absolute-uri, external...
  print(String.fromCharCodes(record.payload));
}

Underneath sits ndef_record 1.5.0, which models NdefMessage and NdefRecord with typeNameFormat, type, identifier and payload fields plus a TypeNameFormat enum — the standard well-known, MIME, absolute-URI and external type name formats. When you need low-level tag technology instead, Android exposes NfcAAndroid, NfcBAndroid, NfcFAndroid, NfcVAndroid, IsoDepAndroid, MifareClassicAndroid and MifareUltralightAndroid, while iOS exposes FeliCaIos, MiFareIos, Iso15693Ios and Iso7816Ios.

Reading versus writing

The Android-side classes NdefAndroid and NdefFormatableAndroid mirror the platform’s android.nfc.Ndef and android.nfc.NdefFormatable: a tag already carrying an NDEF message reads and rewrites through Ndef, while a blank or differently formatted tag must go through NdefFormatable first. iOS splits the same way with NdefIos. The Ndef abstraction tells you what you are allowed to do — isWritable and maxSize are plain getters, write(message: ...) overwrites the tag, and writeLock() makes it permanently read-only. In practice, read-only use cases — posters, inventory labels, badges — only ever need Ndef.from(tag) plus the record parser above, which keeps you clear of write-permission dialogs entirely.

Where tag reading fits

The classic deployments are event posters and flyers that open a URL, warehouse or asset tags scanned for inventory, business cards, and check-in systems where a phone tap replaces a QR scan. Because reading is passive and needs no pairing, it works well in kiosk flows — just remember the platform realities: iOS sessions only start while your app is foregrounded with the entitlement granted, and Android requires the manifest permission plus the screen unlocked for most tag types.

Takeaway: add the NFC permission and iOS reader entitlement, call NfcManager.instance.startSession(...) with the right polling options, and decode payloads through Ndef.from(tag) and ndef_record’s NdefRecord fields to turn any tap into structured data.