react-native-document-scanner-plugin
React Native plugin that opens a native camera scanner to detect, crop, and export documents on iOS and Android.
Repository Health
Technical Analysis
react-native-document-scanner-plugin adds native document scanning to React Native apps. On Android it delegates to Google ML Kit’s GmsDocumentScanning client, launched through an ActivityResultLauncher-based IntentSender flow; on iOS it wraps a separate ios-document-scanner git submodule behind a Swift bridge. Both paths are unified behind a single TurboModule-backed JS API, scanDocument(), that returns a Promise resolving to the cropped scan output and a success/cancel status.
The library exposes a deliberately small surface: one function and three options (croppedImageQuality, maxNumDocuments, responseType). Output can come back as image file paths or base64 strings, and an Expo config plugin auto-injects the required camera usage description into Info.plist for managed Expo projects. It targets apps that need clean, edge-detected document photos — receipts, forms, business cards — without building a custom camera-cropping UI from scratch.
What You Get
- A single
scanDocument()call that opens a native, edge-detecting camera UI on both iOS and Android - Cropped output returned as either file paths or base64-encoded images via the
responseTypeoption - An Android-only
maxNumDocumentslimit for capping multi-page scan sessions - An Expo config plugin that auto-adds the
NSCameraUsageDescriptionInfo.plist entry for managed projects - A typed TurboModule spec (
NativeDocumentScanner.ts) with codegen-backed native bridging instead of the legacy RN bridge
Common Use Cases
- Capturing and cropping paper receipts for expense-tracking or finance apps
- Scanning business cards or notes, using
maxNumDocumentsto cap front/back captures - Collecting ID documents or signed forms as part of an onboarding or submission flow
- Uploading scanned pages directly as base64 without touching the device filesystem
Under The Hood
Architecture
The JS layer (src/index.tsx) is a one-function passthrough to NativeDocumentScanner, a TurboModule spec (TurboModuleRegistry.getEnforcing<Spec>('DocumentScanner')) generated via the DocumentScannerSpec codegen config. Each platform implements that spec independently with no shared native abstraction: the Kotlin DocumentScannerModule builds a GmsDocumentScannerOptions object and drives Google ML Kit’s document scanner through an ActivityResultLauncher/IntentSenderRequest flow, resolving a WritableMap on success or cancel; the Swift RNDocumentScanner instead delegates to a DocScanner type that lives in a separate git submodule (ios/DocScanner, from the ios-document-scanner repo) and translates its callback-based startScan API into a Promise. The only thing unifying the two platforms is the shared JS-facing ScanDocumentResponse contract; changing that Spec means updating both native implementations in lockstep with no intermediate shared layer.
Tech Stack
Built with react-native-builder-bob (module + typescript build targets) on TypeScript 5.8, targeting React Native’s new-architecture TurboModule/Codegen infrastructure. Android depends on Google Play Services’ ML Kit document scanner client and AndroidX Activity Result APIs; iOS is Swift plus an Objective-C++ bridge (DocumentScanner.mm/.h) packaged via a CocoaPods podspec that pulls in the ios-document-scanner submodule. An expo-plugin package, built with expo-module-scripts, ships alongside the core library for Expo config-plugin support. The repo is a Yarn 3 workspace orchestrated with Turborepo, using lefthook for git hooks and ESLint 9 + Prettier for style.
Code Quality
TypeScript typing is consistent and the public Spec/options/response interfaces are documented with JSDoc, but there is effectively no automated test coverage: the only test file, src/__tests__/index.test.tsx, contains a single it.todo('write a test') despite Jest and the react-native preset being fully configured. The only CI workflow present is a manual, workflow_dispatch-triggered npm publish job — there is no CI that runs lint, typecheck, or tests on push or PR. Error handling is present but shallow: native failures reject the Promise with a message, but there’s no retry or fallback path on either platform.
API Design
The public API is minimal by design: one function, three optional parameters, and two small enums (ResponseType, ScanDocumentResponseStatus) instead of magic strings. Defaults are sensible (100% quality, file-path responses, no page cap), so a working scan can be wired up with a single import and a single call, and the Expo plugin removes the one manual native-config step (camera usage description) most consumers would otherwise have to do by hand.