Suppose you're building a mobile app with a form for passport details. You could ask users to type everything in. Another option is to scan the document and read data from its chip.
In this guide, we'll build that flow in TypeScript:
Scan the MRZ → read the NFC chip → return document data to your app.
We'll use capacitor-id-reader, a plugin I built for this workflow on iOS and Android. You can connect the example to your existing Capacitor UI.
1. Install and configure the plugin
You'll need Capacitor 8, Node.js 22+, and a physical NFC phone running iOS 15+ or Android API 26+. Use a compatible electronic passport or ID card.
npm install capacitor-id-reader
npx cap sync
On iOS, configure your app target:
- Enable Near Field Communication Tag Reading, including it in your signing profile.
- Add
TAGto thecom.apple.developer.nfc.readersession.formatsentitlement. - Add
NFCReaderUsageDescriptionandNSCameraUsageDescriptiontoInfo.plist. - Add
A0000002471001to thecom.apple.developer.nfc.readersession.iso7816.select-identifiersarray inInfo.plist.
On Android, the plugin declares camera and NFC permissions. Enable NFC on the phone; camera permission is requested when scanning.
Check the native setup instructions when configuring your project.
2. Scan the MRZ and read the chip
The MRZ is the machine-readable text printed on the document. It provides the document number, date of birth and expiry date used for chip access.
import { NfcIdReader, parseMrz } from 'capacitor-id-reader';
export async function readPassport(includePhoto = false) {
const { available, enabled } = await NfcIdReader.isAvailable();
if (!available) {
throw new Error('NFC reading is unavailable on this device.');
}
if (!enabled) {
throw new Error('Enable NFC before reading the document.');
}
const scan = await NfcIdReader.scanMrz({
prompt: 'Position the document’s MRZ inside the camera view',
cancelLabel: 'Go back',
});
const accessData = parseMrz(scan.lines);
if (!accessData) {
throw new Error('The MRZ could not be parsed. Please scan it again.');
}
// Show an instruction to hold the document against the phone.
if (includePhoto) {
return NfcIdReader.readDocument({
...accessData,
dataGroups: ['DG1', 'DG2'],
});
}
return NfcIdReader.readDocument(accessData);
}
DG1 contains document details; DG2 contains the face image. The default call reads DG1. Requesting the photo is an explicit choice.
3. Connect the flow to a button
Treat reading as a user action with loading, success and failure states. Add this controller alongside readPassport, and connect its callbacks to your component state:
type PassportData = Awaited<ReturnType<typeof readPassport>>;
let reading = false;
export async function handleReadClick(ui: {
setBusy: (busy: boolean) => void;
showDocument: (document: PassportData) => void;
showError: (message: string) => void;
}) {
if (reading) return;
reading = true;
ui.setBusy(true);
try {
const document = await readPassport();
ui.showDocument(document);
} catch (error) {
ui.showError(
error instanceof Error ? error.message : 'Reading failed. Please retry.',
);
} finally {
reading = false;
ui.setBusy(false);
}
}
Disable the button while busy. On success, use showDocument to populate a review screen before the user submits anything.
The catch block is a starting point. For your app's UX, map the exported ENfcIdReaderError codes to appropriate messages, including cancellation and denied camera permission.
4. Optionally display the face image
Call readPassport(true) to request DG2 in the same NFC session. Then prepare a JPEG URL for your image component:
const document = await readPassport(true);
const photo = document.faceImage;
const jpeg = photo?.previewJpegBase64 ?? (
photo?.mimeType === 'image/jpeg' ? photo.base64 : undefined
);
const photoUrl = jpeg ? `data:image/jpeg;base64,${jpeg}` : undefined;
Bind photoUrl to an image when it exists. A JPEG 2000 original may need a decoder. See the DG2 documentation for platform details and failure behavior.
Before shipping
A successful chip read does not establish document authenticity, ownership or liveness. The plugin does not implement passive authentication or certificate trust validation. Reading data alone must not be treated as verified identity.
Compatibility and validation work remain. Test your exact package version with representative documents on physical devices, including interrupted reads and retries. Review the audit and known limitations, and avoid logging document data or face images.
You now have a camera-to-NFC flow to connect to your document form. Map the returned data to your fields and let the user review it before submission.
The full API, native setup and device-test example are in the capacitor-id-reader repository.
Top comments (0)