react-native-scan-aadhaar-card
v0.3.5
Published
High-accuracy native Aadhaar (UIDAI) QR code scanner for React Native. Supports the dense Secure QR (2019+) and legacy XML formats, gallery import, torch, pinch-to-zoom and tap-to-focus.
Maintainers
Readme
react-native-scan-aadhaar-card
High-accuracy native Aadhaar (UIDAI) QR code scanner for React Native.
Physical Aadhaar cards use the densest standard QR code (version 40, 177×177 modules), which most generic scanner libraries fail to read reliably. This library wraps purpose-built native scanners:
Features
- Full-screen native scanner UI: dimmed viewfinder, torch, pinch-to-zoom, tap-to-focus.
- Scan from the camera or import from the gallery / photo library.
- Returns parsed, privacy-safe fields (masked Aadhaar number, name, DOB, address, hashes, optional photo).
- Customizable title, hints, accent / overlay colors, toggles, timeout, and error messages.
- TypeScript types included.
Installation
npm install react-native-scan-aadhaar-card
# or
yarn add react-native-scan-aadhaar-cardiOS
cd ios && pod installAdd the usage description strings to your app's Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need the camera to scan your Aadhaar QR code.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>We need photo access to scan an Aadhaar QR from your gallery.</string>Minimum iOS deployment target: 13.0.
Android
The android.permission.CAMERA permission and the scanner Activity are merged
automatically from the library's manifest — no manual edits required.
Minimum minSdkVersion: 24.
After
yarn add/npm install, you MUST rebuild native code — Metro JS reload is not enough. Runnpx react-native run-androidorcd android && ./gradlew :app:assembleDebug.
The camera permission is requested at runtime by the scanner itself; you do not need to request it beforehand.
Compatibility matrix
| React Native | Architecture | JS bridge | Android native | Supported |
|---|---|---|---|---|
| 0.74 | Old Arch (newArchEnabled=false) | NativeModules.ScanAadhaarCard fallback | ScanAadhaarCardModule via ReactPackage | ✅ |
| 0.76+ | New Arch (newArchEnabled=true) | TurboModuleRegistry.get('ScanAadhaarCard') | Codegen NativeScanAadhaarCardSpec | ✅ |
| 0.72 – 0.73 | Old Arch | NativeModules fallback | ScanAadhaarCardModule via ReactPackage | ✅ |
| Consumer toolchain | How the library adapts |
|---|---|
| Kotlin 1.9.x | Reads rootProject.ext.kotlinVersion first; fallback 1.9.24 in android/gradle.properties |
| AGP 8.6+ | No pinned AGP in library — uses consumer Gradle plugin version |
| Expo 50–52 | Same autolinking; rebuild native after install |
NativeScanAadhaarCard.ts resolves the module lazily: TurboModule first, then NativeModules — so RN 0.74 Old Arch and RN 0.76+ New Arch both work without app-side bridge code.
No manual configuration needed:
- No
patch-package - No bridge Kotlin files in your app
- No
android: nullinreact-native.config.js - No manual
settings.gradle/app/build.gradleentries - No
implementation "androidx.databinding:viewbinding:..."in your app - No
useTurboModules/DefaultTurboModuleManagerDelegatechanges
Android + React Native <Modal>
The native scanner runs as a full-screen Activity. If you call scanAadhaar()
while a RN <Modal> is visible, the scanner may appear behind the modal on some
devices. Hide the modal first:
setModalVisible(false);
// Wait one frame for the modal to dismiss before launching the scanner
await new Promise(resolve => setTimeout(resolve, 100));
const data = await scanAadhaar();Usage
import {
scanAadhaar,
fullAddress,
displayGender,
AadhaarScanError,
} from 'react-native-scan-aadhaar-card';
async function handleScan() {
try {
const data = await scanAadhaar({
title: 'Scan Aadhaar QR',
accentColor: '#2E7D32',
enableGallery: true,
enableTorch: true,
includePhoto: true,
});
console.log(data.name, data.uid); // "Rahul Kumar" "XXXX XXXX 1234"
console.log(displayGender(data)); // "Male"
console.log(fullAddress(data)); // "123, Main Street, ..."
} catch (e) {
if (e instanceof AadhaarScanError) {
if (e.code === 'CANCELLED') return; // user closed the scanner
console.warn(e.code, e.message);
} else {
throw e;
}
}
}API
scanAadhaar(options?: AadhaarScanOptions): Promise<AadhaarData>
Launches the native full-screen scanner. Resolves with the parsed data, or rejects
with an AadhaarScanError.
fullAddress(data: AadhaarData): string
Comma-separated single-line address from the populated address fields.
displayGender(data: AadhaarData): string
Maps "M" / "F" / "T" to "Male" / "Female" / "Transgender".
Options
| Option | Type | Default | Description |
| --- | --- | --- | --- |
| title | string | "Scan Aadhaar QR" | Title shown at the top of the scanner. |
| scanHint | string | "Align the QR code within the frame" | Primary hint under the frame. |
| zoomHint | string | "Tap to focus · Pinch to zoom" | Secondary hint. |
| enableGallery | boolean | true | Show the "scan from gallery" button. |
| enableTorch | boolean | true | Show the torch / flash toggle. |
| accentColor | string | platform default | Color of the scan frame + scan line. |
| overlayColor | string | ~55% black | Dimmed overlay color. Accepts #RRGGBB or #AARRGGBB. |
| includePhoto | boolean | false | Include the embedded card photo as base64 PNG (iOS only for now). |
| includeRawValue | boolean | false | Include the exact raw QR string (PII — see below). |
| timeoutMs | number | 30000 | Hard cap on a single gallery decode. |
| invalidQrMessage | string | — | Override the "not a valid Aadhaar QR" message. |
| noQrInImageMessage | string | — | Override the "no QR in image" message. |
| scanTimeoutMessage | string | — | Override the timeout message. |
| permissionDeniedMessage | string | — | Override the permission-denied message. |
Result — AadhaarData
| Field | Notes |
| --- | --- |
| uid | Masked Aadhaar number, e.g. "XXXX XXXX 1234" (full number is never in the QR). |
| name, gender, dateOfBirth, yearOfBirth | Identity fields. gender is the raw M/F/T code. |
| careOf, house, street, landmark, locality, vtc, district, state, pincode | Address fields. |
| mobileHash, emailHash | SHA-256 hashes (not reversible), when present. |
| photoBase64? | Base64 PNG of the embedded photo, only when includePhoto is set (iOS). |
| rawValue? | Exact scanned QR string, only when includeRawValue is set. Treat as PII. |
About rawValue and privacy
The full Aadhaar number is never present in the QR — only the last 4 digits are
recoverable. Mobile/email are stored as SHA-256 hashes. The raw QR payload is
considered PII (for new cards it's the long Secure QR decimal string), so it is
not returned by default. Enable includeRawValue: true only if you genuinely
need it; even then it is never logged by the native code.
Error handling
scanAadhaar rejects with an AadhaarScanError exposing a .code:
| Code | Meaning |
| --- | --- |
| CANCELLED | The user closed the scanner. |
| PERMISSION_DENIED | Camera permission was denied. |
| UNAVAILABLE | No host Activity/ViewController, or a scan is already in progress. |
| UNKNOWN | Unexpected native failure. |
Note: an unreadable / non-Aadhaar QR does not reject — the scanner shows an inline message and keeps scanning so the user can retry.
Architecture support
Works under both the legacy bridge and the New Architecture (TurboModules / bridgeless via the interop layer). No extra configuration required.
Credits
QR decode/parse pipeline reverse-engineered from physical UIDAI Aadhaar cards.
See docs/AADHAAR_QR_SCANNING.md for the format notes.
License
MIT
Changelog
v0.3.5
No API changes. Rebuild native after upgrading.
| Area | v0.3.4 | v0.3.5 |
|---|---|---|
| Android distribution | Precompiled AAR | Kotlin source (consumer Gradle compile) |
| RN 0.74 Old Arch | NativeModules fallback | Unchanged |
| RN 0.76+ New Arch | TurboModule codegen | Unchanged |
| Kotlin / AGP | AAR wrapper + pinned deps | Consumer kotlinVersion 1.9.x + AGP 8.6+ |
v0.2.0
No breaking changes. API, options, and return types are identical to v0.1.0.
What changed
| Area | v0.1.0 | v0.2.0 |
|---|---|---|
| scanAadhaar() API | — | Unchanged |
| AadhaarScanOptions props | — | Unchanged |
| AadhaarData fields | — | Unchanged |
| Error codes | CANCELLED, PERMISSION_DENIED, UNAVAILABLE, UNKNOWN | Added: NO_QR_FOUND, INVALID_QR, TIMEOUT |
| Android zxing-cpp version | 3.0.2 | 2.3.0 (see note below) |
| Android Gradle Plugin | not pinned | 8.6.0 |
| Kotlin version | not pinned | 1.9.24 (configurable via gradle.properties) |
| iOS native code shipped | Swift source files | Precompiled XCFramework binary |
| Android native code shipped | Kotlin source files | Precompiled AAR |
| JS code shipped | TypeScript source | Minified compiled JS only |
New error codes in v0.2.0
The following AadhaarScanError codes were added. They were previously returned as UNKNOWN:
| Code | Meaning |
|---|---|
| NO_QR_FOUND | Gallery image was processed but contained no QR code. |
| INVALID_QR | A QR code was found but it is not a valid Aadhaar QR. |
| TIMEOUT | Gallery decode exceeded timeoutMs (default 30 000 ms). |
If you previously caught all errors with e.code === 'UNKNOWN', update your handler to also check for these codes if you want to show specific messages to the user.
Android zxing-cpp version note
v0.2.0 ships with zxing-cpp:android:2.3.0 for compatibility with Android Gradle Plugin 8.6 and older NDK toolchains.
Upgrading to v0.3.0 (planned): When you upgrade your project to AGP ≥ 8.7, install v0.3.0 of this library which will restore zxing-cpp:3.0.2. That version decodes dense Aadhaar Secure QR codes more reliably on newer hardware. No API changes are planned.
// When ready to upgrade:
"react-native-scan-aadhaar-card": "^0.3.0"v0.1.0
Initial release.
