# @capgo/capacitor-nfc
<a href="https://capgo.app/"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-nfc" alt="Capgo - Instant updates for Capacitor" /></a>

<div align="center">
  <h2><a href="https://capgo.app/?ref=plugin_nfc"> ➡️ Get Instant updates for your App with Capgo</a></h2>
  <h2><a href="https://capgo.app/consulting/?ref=plugin_nfc"> Missing a feature? We’ll build the plugin for you 💪</a></h2>
</div>

Native NFC tag detection, reading, and writing for Capacitor apps on iOS and Android.

Modern Capacitor port of the battle-tested [phonegap-nfc](https://github.com/chariotsolutions/phonegap-nfc) plugin, aligned with Capgo conventions and tooling.

## Supported Tag Types

- **Standard NFC Forum Tags** (Type 1-4)
- **MIFARE Ultralight** - Full support for reading NDEF data from MIFARE Ultralight tags, including EV1 and NTAG variants. The plugin automatically detects MIFARE Ultralight cards and extracts NDEF messages in addition to standard NFC tags.

## Documentation

The most complete documentation will live on the Capgo docs portal. Until then, explore the TypeScript definitions (`src/definitions.ts`) and run the included example app for a tour of the API.

## Compatibility

| Plugin version | Capacitor compatibility | Maintained |
| -------------- | ----------------------- | ---------- |
| v8.\*.\*       | v8.\*.\*                | ✅          |
| v7.\*.\*       | v7.\*.\*                | On demand   |
| v6.\*.\*       | v6.\*.\*                | ❌          |
| v5.\*.\*       | v5.\*.\*                | ❌          |

> **Note:** The major version of this plugin follows the major version of Capacitor. Use the version that matches your Capacitor installation (e.g., plugin v8 for Capacitor 8). Only the latest major version is actively maintained.

## Install

You can use our AI-Assisted Setup to install the plugin. Add the Capgo skills to your AI tool using the following command:

```bash
npx skills add https://github.com/cap-go/capacitor-skills --skill capacitor-plugins
```

Then use the following prompt:

```text
Use the `capacitor-plugins` skill from `cap-go/capacitor-skills` to install the `@capgo/capacitor-nfc` plugin in my project.
```

If you prefer Manual Setup, install the plugin by running the following commands and follow the platform-specific instructions below:

```bash
npm install @capgo/capacitor-nfc
npx cap sync
```

Remember to add the required platform configuration:

- **Android:** ensure your `AndroidManifest.xml` declares the `android.permission.NFC` permission.
- **iOS:** add `NFCReaderUsageDescription` to your app `Info.plist` to explain why NFC access is needed.
- **iOS (`iosSessionType: 'tag'`):** to use the tag session type you must also add the `Near Field Communication Tag Reader Session Formats` entitlement (`com.apple.developer.nfc.readersession.formats`) to your app's entitlements file and include the `TAG` format (and optionally `NDEF`). Without this entitlement the session will fail to start.

## Usage

### Checking NFC hardware support

Before attempting to use NFC features, you can check if the device has NFC hardware:

```ts
import { CapacitorNfc } from '@capgo/capacitor-nfc';

const { supported } = await CapacitorNfc.isSupported();
if (!supported) {
  console.warn('This device does not have NFC hardware');
  // Hide NFC features in your UI
} else {
  // Check if NFC is currently enabled
  const { status } = await CapacitorNfc.getStatus();
  if (status === 'NFC_DISABLED') {
    console.warn('NFC is disabled. Prompt user to enable it.');
    // Optionally show settings button
  }
}
```

### Reading NDEF tags (default behavior)

```ts
import { CapacitorNfc } from '@capgo/capacitor-nfc';

await CapacitorNfc.startScanning({
  invalidateAfterFirstRead: false, // keep the session open so we can write later
  alertMessage: 'Hold a tag near the top of your device.',
});

const listener = await CapacitorNfc.addListener('nfcEvent', (event) => {
  console.info('Tag type:', event.type);
  console.info('First record:', event.tag?.ndefMessage?.[0]);
});

// Later, write a simple text record back to the tag
const encoder = new TextEncoder();
const langBytes = Array.from(encoder.encode('en'));
const textBytes = Array.from(encoder.encode('Hello Capgo'));
const payload = [langBytes.length & 0x3f, ...langBytes, ...textBytes];

await CapacitorNfc.write({
  allowFormat: true,
  records: [
    {
      tnf: 0x01,
      type: [0x54], // 'T'
      id: [],
      payload,
    },
  ],
});

await listener.remove();
await CapacitorNfc.stopScanning();
```

### Reading raw tags (iOS) - Get UID from unformatted tags

```ts
import { CapacitorNfc } from '@capgo/capacitor-nfc';

// Use 'tag' session type to read raw (non-NDEF) tags
await CapacitorNfc.startScanning({
  iosSessionType: 'tag', // Enable raw tag reading on iOS
  alertMessage: 'Hold your card near the device',
});

const listener = await CapacitorNfc.addListener('nfcEvent', (event) => {
  console.info('Tag detected:', event.type); // 'tag' or 'ndef'
  
  // Read the UID (identifier) - works for both NDEF and raw tags
  if (event.tag?.id) {
    const uid = event.tag.id.map(byte => byte.toString(16).padStart(2, '0').toUpperCase()).join(':');
    console.info('Tag UID:', uid); // e.g., "04:A1:B2:C3:D4:E5:F6"
  }
  
  // If the tag has NDEF data, it will also be available
  if (event.tag?.ndefMessage) {
    console.info('NDEF records:', event.tag.ndefMessage);
  }
});

await listener.remove();
await CapacitorNfc.stopScanning();
```

### Handling iOS session endings

Listen for `nfcSessionEnd` to distinguish a user cancellation from a session timeout or another Core NFC error. The event is available for both `ndef` and `tag` iOS sessions. A normal first-read completion does not emit this event.

```ts
const sessionEndListener = await CapacitorNfc.addListener('nfcSessionEnd', ({ reason }) => {
  if (reason === 'userCancelled') {
    return;
  }

  console.warn(reason === 'sessionTimeout' ? 'No tag detected. Try again.' : 'The NFC session failed.');
});

// Later, when the listener is no longer needed
await sessionEndListener.remove();
```

## API

<docgen-index>

* [`startScanning(...)`](#startscanning)
* [`stopScanning()`](#stopscanning)
* [`write(...)`](#write)
* [`erase()`](#erase)
* [`makeReadOnly()`](#makereadonly)
* [`share(...)`](#share)
* [`unshare()`](#unshare)
* [`getStatus()`](#getstatus)
* [`showSettings()`](#showsettings)
* [`getPluginVersion()`](#getpluginversion)
* [`isSupported()`](#issupported)
* [`addListener('nfcEvent', ...)`](#addlistenernfcevent-)
* [`addListener('tagDiscovered' | 'ndefDiscovered' | 'ndefMimeDiscovered' | 'ndefFormatableDiscovered', ...)`](#addlistenertagdiscovered--ndefdiscovered--ndefmimediscovered--ndefformatablediscovered-)
* [`addListener('nfcStateChange', ...)`](#addlistenernfcstatechange-)
* [`addListener('nfcSessionEnd', ...)`](#addlistenernfcsessionend-)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)

</docgen-index>

<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->

Public API surface for the Capacitor NFC plugin.

The interface intentionally mirrors the behaviour of the reference PhoneGap
implementation to ease migration while embracing idiomatic Capacitor APIs.

### startScanning(...)

```typescript
startScanning(options?: StartScanningOptions | undefined) => Promise<void>
```

Starts listening for NFC tags.

| Param         | Type                                                                  |
| ------------- | --------------------------------------------------------------------- |
| **`options`** | <code><a href="#startscanningoptions">StartScanningOptions</a></code> |

--------------------


### stopScanning()

```typescript
stopScanning() => Promise<void>
```

Stops the ongoing NFC scanning session.

--------------------


### write(...)

```typescript
write(options: WriteTagOptions) => Promise<void>
```

Writes the provided NDEF records to the last discovered tag.

| Param         | Type                                                        |
| ------------- | ----------------------------------------------------------- |
| **`options`** | <code><a href="#writetagoptions">WriteTagOptions</a></code> |

--------------------


### erase()

```typescript
erase() => Promise<void>
```

Attempts to erase the last discovered tag by writing an empty NDEF message.

--------------------


### makeReadOnly()

```typescript
makeReadOnly() => Promise<void>
```

Attempts to make the last discovered tag read-only.

--------------------


### share(...)

```typescript
share(options: ShareTagOptions) => Promise<void>
```

Shares an NDEF message with another device via peer-to-peer (Android only).

| Param         | Type                                                        |
| ------------- | ----------------------------------------------------------- |
| **`options`** | <code><a href="#sharetagoptions">ShareTagOptions</a></code> |

--------------------


### unshare()

```typescript
unshare() => Promise<void>
```

Stops sharing previously provided NDEF message (Android only).

--------------------


### getStatus()

```typescript
getStatus() => Promise<{ status: NfcStatus; }>
```

Returns the current NFC adapter status.

**Returns:** <code>Promise&lt;{ status: <a href="#nfcstatus">NfcStatus</a>; }&gt;</code>

--------------------


### showSettings()

```typescript
showSettings() => Promise<void>
```

Opens the system settings page where the user can enable NFC.

--------------------


### getPluginVersion()

```typescript
getPluginVersion() => Promise<{ version: string; }>
```

Returns the version string baked into the native plugin.

**Returns:** <code>Promise&lt;{ version: string; }&gt;</code>

--------------------


### isSupported()

```typescript
isSupported() => Promise<{ supported: boolean; }>
```

Checks whether the device has NFC hardware support.

Returns `true` if NFC hardware is present on the device, regardless of
whether NFC is currently enabled or disabled. Returns `false` if the
device does not have NFC hardware.

Use this method to determine if NFC features should be shown in your
app's UI. To check if NFC is currently enabled, use `getStatus()`.

**Returns:** <code>Promise&lt;{ supported: boolean; }&gt;</code>

--------------------


### addListener('nfcEvent', ...)

```typescript
addListener(eventName: 'nfcEvent', listenerFunc: (event: NfcEvent) => void) => Promise<PluginListenerHandle>
```

| Param              | Type                                                              |
| ------------------ | ----------------------------------------------------------------- |
| **`eventName`**    | <code>'nfcEvent'</code>                                           |
| **`listenerFunc`** | <code>(event: <a href="#nfcevent">NfcEvent</a>) =&gt; void</code> |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### addListener('tagDiscovered' | 'ndefDiscovered' | 'ndefMimeDiscovered' | 'ndefFormatableDiscovered', ...)

```typescript
addListener(eventName: 'tagDiscovered' | 'ndefDiscovered' | 'ndefMimeDiscovered' | 'ndefFormatableDiscovered', listenerFunc: (event: NfcEvent) => void) => Promise<PluginListenerHandle>
```

| Param              | Type                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| **`eventName`**    | <code>'tagDiscovered' \| 'ndefDiscovered' \| 'ndefMimeDiscovered' \| 'ndefFormatableDiscovered'</code> |
| **`listenerFunc`** | <code>(event: <a href="#nfcevent">NfcEvent</a>) =&gt; void</code>                                      |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### addListener('nfcStateChange', ...)

```typescript
addListener(eventName: 'nfcStateChange', listenerFunc: (event: NfcStateChangeEvent) => void) => Promise<PluginListenerHandle>
```

| Param              | Type                                                                                    |
| ------------------ | --------------------------------------------------------------------------------------- |
| **`eventName`**    | <code>'nfcStateChange'</code>                                                           |
| **`listenerFunc`** | <code>(event: <a href="#nfcstatechangeevent">NfcStateChangeEvent</a>) =&gt; void</code> |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### addListener('nfcSessionEnd', ...)

```typescript
addListener(eventName: 'nfcSessionEnd', listenerFunc: (event: NfcSessionEndEvent) => void) => Promise<PluginListenerHandle>
```

| Param              | Type                                                                                  |
| ------------------ | ------------------------------------------------------------------------------------- |
| **`eventName`**    | <code>'nfcSessionEnd'</code>                                                          |
| **`listenerFunc`** | <code>(event: <a href="#nfcsessionendevent">NfcSessionEndEvent</a>) =&gt; void</code> |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### Interfaces


#### StartScanningOptions

Options controlling the behaviour of {@link CapacitorNfcPlugin.startScanning}.

| Prop                           | Type                               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| ------------------------------ | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`invalidateAfterFirstRead`** | <code>boolean</code>               | iOS-only: closes the NFC session automatically after the first successful tag read. Defaults to `true`.                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| **`alertMessage`**             | <code>string</code>                | iOS-only: custom message displayed in the NFC system sheet while scanning.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| **`iosSessionType`**           | <code>'tag' \| 'ndef'</code>       | iOS-only: session type to use for NFC scanning. - `'ndef'`: Uses NFCNDEFReaderSession (default). Only detects NDEF-formatted tags. - `'tag'`: Uses NFCTagReaderSession. Detects both NDEF and non-NDEF tags (e.g., raw MIFARE tags). Allows reading UID from unformatted tags. **Requires** the `Near Field Communication Tag Reader Session Formats` entitlement in your app with the `TAG` format included. Without it the session will fail to start and the promise will reject with a `NO_NFC` error code. Defaults to `'ndef'` for backward compatibility. |
| **`iosPollingOptions`**        | <code>NfcIosPollingOption[]</code> | iOS-only: polling options to use for NFC scanning. Defaults to `['iso14443', 'iso15693']`. Apps that scan FeliCa tags can opt in to `'iso18092'` after configuring the required FeliCa entitlement and system codes.                                                                                                                                                                                                                                                                                                                                             |
| **`androidReaderModeFlags`**   | <code>number</code>                | Android-only: raw flags passed to `NfcAdapter.enableReaderMode`. Defaults to enabling all tag types and Android's NDEF discovery so that detected tags remain available for writing and formatting.                                                                                                                                                                                                                                                                                                                                                              |


#### WriteTagOptions

Options used when writing an NDEF message on the current tag.

| Prop              | Type                      | Description                                                                                          |
| ----------------- | ------------------------- | ---------------------------------------------------------------------------------------------------- |
| **`records`**     | <code>NdefRecord[]</code> | Array of records that compose the NDEF message to be written.                                        |
| **`allowFormat`** | <code>boolean</code>      | When `true`, the plugin attempts to format NDEF-formattable tags before writing. Defaults to `true`. |


#### NdefRecord

JSON structure representing a single NDEF record.

Mirrors the data format returned by the legacy Cordova implementation and
uses integer arrays instead of strings to preserve the original payload
bytes.

| Prop          | Type                  | Description                                             |
| ------------- | --------------------- | ------------------------------------------------------- |
| **`tnf`**     | <code>number</code>   | Type Name Format identifier.                            |
| **`type`**    | <code>number[]</code> | Type field expressed as an array of byte values.        |
| **`id`**      | <code>number[]</code> | Record identifier expressed as an array of byte values. |
| **`payload`** | <code>number[]</code> | Raw payload expressed as an array of byte values.       |


#### ShareTagOptions

Options used when sharing an NDEF message with another device using Android Beam / P2P mode.

| Prop          | Type                      |
| ------------- | ------------------------- |
| **`records`** | <code>NdefRecord[]</code> |


#### PluginListenerHandle

| Prop         | Type                                      |
| ------------ | ----------------------------------------- |
| **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |


#### NfcEvent

Generic NFC discovery event dispatched by the plugin.

| Prop       | Type                                                  |
| ---------- | ----------------------------------------------------- |
| **`type`** | <code><a href="#nfceventtype">NfcEventType</a></code> |
| **`tag`**  | <code><a href="#nfctag">NfcTag</a></code>             |


#### NfcTag

Representation of the full tag information returned by the native layers.

Supports standard NFC Forum tags as well as MIFARE Ultralight cards (including
EV1 and NTAG variants). NDEF data is automatically extracted from MIFARE Ultralight
tags when available.

| Prop                  | Type                              | Description                                                                            |
| --------------------- | --------------------------------- | -------------------------------------------------------------------------------------- |
| **`id`**              | <code>number[]</code>             | Raw identifier bytes for the tag.                                                      |
| **`techTypes`**       | <code>string[]</code>             | List of Android tech strings (e.g. `android.nfc.tech.Ndef`).                           |
| **`type`**            | <code>string \| null</code>       | Human readable tag type when available (e.g. `NFC Forum Type 2`, `MIFARE Ultralight`). |
| **`maxSize`**         | <code>number \| null</code>       | Maximum writable size in bytes for tags that expose NDEF information.                  |
| **`isWritable`**      | <code>boolean \| null</code>      | Indicates whether the tag can be written to.                                           |
| **`canMakeReadOnly`** | <code>boolean \| null</code>      | Indicates whether the tag can be permanently locked.                                   |
| **`ndefMessage`**     | <code>NdefRecord[] \| null</code> | Array of NDEF records discovered on the tag.                                           |


#### NfcStateChangeEvent

Event emitted whenever the NFC adapter availability changes.

| Prop          | Type                                            |
| ------------- | ----------------------------------------------- |
| **`status`**  | <code><a href="#nfcstatus">NfcStatus</a></code> |
| **`enabled`** | <code>boolean</code>                            |


#### NfcSessionEndEvent

Event emitted when an iOS NFC reader session ends.

This event is not emitted when a session closes normally after its first successful read.

| Prop         | Type                                                              |
| ------------ | ----------------------------------------------------------------- |
| **`reason`** | <code>'userCancelled' \| 'sessionTimeout' \| 'invalidated'</code> |


### Type Aliases


#### NfcIosPollingOption

iOS-specific polling options for NFC scanning sessions.
These options determine the type of tags that a reader session
should detect during a polling sequence.

- 'iso14443': Polls for ISO 14443 tags
- 'iso15693': Polls for ISO 15693 tags
- 'iso18092': Polls for ISO 18092 tags
- 'pace': Polls for PACE tags (available on iOS 16 and later)

<code>'iso14443' | 'iso15693' | 'iso18092' | 'pace'</code>


#### NfcStatus

Possible NFC adapter states returned by {@link CapacitorNfcPlugin.getStatus}.

Matches the constants provided by the original PhoneGap NFC plugin for
compatibility with existing applications.

<code>'NFC_OK' | 'NO_NFC' | 'NFC_DISABLED' | 'NDEF_PUSH_DISABLED'</code>


#### NfcEventType

Event type describing the kind of NFC discovery that happened.

- `tag`: A generic NFC tag (no NDEF payload).
- `ndef`: A tag exposing an NDEF payload.
- `ndef-mime`: An NDEF tag that matched one of the MIME type filters.
- `ndef-formatable`: A tag that can be formatted to NDEF.

<code>'tag' | 'ndef' | 'ndef-mime' | 'ndef-formatable'</code>

</docgen-api>
