npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jieonist/vision-camera-ocr-scanner

v0.1.0

Published

On-device structured OCR scanner (MRZ passport, credit card, business card) for React Native — built on VisionCamera + Nitro

Readme

@jieonist/vision-camera-ocr-scanner

On-device, structured OCR scanner for React Native — scan a passport (MRZ), credit card, or business card with the camera and get back structured data, not just raw text.

Built as a react-native-vision-camera frame processor plugin on top of Apple Vision (iOS) and Google ML Kit (Android), powered by Nitro Modules for zero-copy, high-FPS frame processing.

📖 Documentation: https://g1d5ny.github.io/vision-camera-ocr-scanner/ (한국어)

🚧 Status: early development. MRZ (passport), credit-card, and business-card scanning work today on iOS and Android; the receipt mode is on the Roadmap. Full docs: https://g1d5ny.github.io/vision-camera-ocr-scanner/

Why this library

  • 🆓 Free & on-device — no API keys, no servers, no per-scan fees. Nothing leaves the device.
  • 🔒 Privacy-first — great for apps that can't send card/ID/receipt images to a cloud OCR service.
  • 🏗️ New Architecture native — Nitro + Fabric, fast per-frame processing.
  • 🧩 Structured output — MRZ fields, card number, contact fields — already parsed.

Not trying to be a paid enterprise KYC SDK (BlinkID, Dynamsoft, Scanbot). This is the "good enough, free, on-device" option for indie and small apps.

Requirements

  • React Native New Architecture (Fabric + TurboModules/Nitro) — required, no old-architecture support.
  • react-native-vision-camera v5+.
  • iOS 15+ / Android (ML Kit).

Platforms

| Platform | Supported | |---|---| | Bare React Native (CLI) | ✅ | | Expo dev build / prebuild | ✅ (autolinking — a dedicated config plugin is on the roadmap) | | Expo Go | ❌ (uses custom native code) |

Installation

npm install @jieonist/vision-camera-ocr-scanner react-native-vision-camera react-native-nitro-modules react-native-nitro-image react-native-worklets react-native-vision-camera-worklets
# or with yarn
yarn add @jieonist/vision-camera-ocr-scanner react-native-vision-camera react-native-nitro-modules react-native-nitro-image react-native-worklets react-native-vision-camera-worklets

iOS:

cd ios && pod install

Expo (dev build) — this package needs no config plugin of its own (it autolinks); only vision-camera's plugin is required:

// app.json / app.config.js
{
  "expo": {
    "plugins": [
      ["react-native-vision-camera", { "cameraPermissionText": "Allow camera to scan documents" }]
    ]
  }
}
npx expo prebuild && npx expo run:ios   # or run:android

Usage

The native side does OCR onlyscan(frame, options?) returns recognized text lines. You then structure them on the JS thread with parseMrz / parseCard (the parsers aren't worklet-safe). This keeps every mode on one native call, with no extra native dependency.

scan() performs real OCR on every call (no internal throttling) and by default recognizes only the central band of the frame — the middle half under a centered guide box, which preserves glyph detail and drops background text. Pass { roi: 'full' } to recognize the whole frame, and throttle calls yourself in the worklet (see below).

import { useCallback, useMemo, useState } from 'react';
import {
  Camera,
  useCameraDevice,
  useCameraPermission,
  useFrameOutput,
} from 'react-native-vision-camera';
import { scheduleOnRN } from 'react-native-worklets';
import {
  getOcrScanner,
  parseCard,
  type CardResult,
} from '@jieonist/vision-camera-ocr-scanner';

export function CardScanner() {
  const { hasPermission } = useCameraPermission();
  const device = useCameraDevice('back');
  const scanner = useMemo(() => getOcrScanner(), []);
  const [card, setCard] = useState<CardResult | null>(null);

  const onLines = useCallback((lines: string[]) => {
    const parsed = parseCard(lines); // or parseMrz(lines)
    if (parsed?.number) setCard(parsed);
  }, []);

  const frameOutput = useFrameOutput({
    pixelFormat: 'yuv',
    onFrame: (frame) => {
      'worklet';
      try {
        // scan() runs real OCR (hundreds of ms) on every call — throttle it.
        const g = globalThis as unknown as { __ocrFrameCount?: number };
        g.__ocrFrameCount = (g.__ocrFrameCount ?? 0) + 1;
        if (g.__ocrFrameCount % 5 !== 0) return;
        const ocr = scanner.scan(frame); // native OCR → { text, lines }
        if (ocr.lines.length > 0) scheduleOnRN(onLines, ocr.lines);
      } finally {
        frame.dispose();
      }
    },
  });

  if (!hasPermission || device == null) return null;
  return (
    <Camera style={{ flex: 1 }} device={device} isActive outputs={[frameOutput]} />
  );
}

Parsers

| Function | Returns | Self-validation | |---|---|---| | parseMrz(lines) | MrzResult \| null — passport/ID fields | ICAO 9303 check digits | | parseCard(lines) | CardResult \| null — number, brand, expiry, holder | Luhn checksum | | parseBusinessCard(lines) | BusinessCardResult \| null — name, company, title, phones, email, website, address | none (heuristics) — pair with its scan session | | detectDocument(lines) | DetectedDocument \| null — auto-detect MRZ, card, or business card | validates MRZ/card; bizcard needs an email | | detectBrand(number) | brand string from a known card number | — |

For a "scan any document" flow, detectDocument(lines) returns { type: 'mrz' | 'card', valid, data } or { type: 'bizcard', data }. Self-validating documents win (MRZ over card on ambiguous ties); a business card is the guarded last resort and only detects when an email is present — a phone number alone would make almost any text "detect" as one. When your screen already knows the type, call the specific parser — fewer false positives and a mode-specific guide box.

See the docs for the full MrzResult, CardResult, and BusinessCardResult shapes.

Native result type

interface OcrResult {
  /** Recognized text joined with newlines. */
  text: string;
  /** Recognized lines, ordered top-to-bottom. Feed these to parseMrz / parseCard. */
  lines: string[];
  /**
   * Layout box per line (same order as `lines`): x/y/width/height normalized
   * 0..1 against the scanned region, upright, top-left origin. Pass to
   * parseBusinessCard(lines, lineItems) so text size becomes a signal.
   */
  lineItems: OcrLine[];
}

Permissions

This library uses the camera via VisionCamera — request permission as usual:

import { Camera } from 'react-native-vision-camera';
const status = await Camera.requestCameraPermission();

Add the platform strings:

  • iOS — NSCameraUsageDescription in Info.plist.
  • Android — android.permission.CAMERA in AndroidManifest.xml.

Accuracy & limitations

  • On-device OCR is good enough for autofill / convenience, not guaranteed for high-stakes verification. Always let users confirm/edit results.
  • Embossed (raised) card numbers, glare, and worn documents reduce accuracy.
  • For full multi-template ID parsing or certified liveness/KYC, use a commercial SDK.

Handling sensitive data

[!WARNING] Never log the result objects. CardResult.lines / MrzResult.lines contain the raw OCR lines — including the full card number (PAN) and passport fields. Logging a result object whole (e.g. console.log(parsed)) leaks that data into device logs and crash/analytics pipelines. Log only what you need (e.g. parsed.valid, parsed.brand), and drop the result from state as soon as the flow is done.

Roadmap

  • [x] MRZ mode (checksum self-validates) — iOS & Android
  • [x] Credit card mode (number + expiry + brand, Luhn) — iOS & Android
  • [x] Business card → contact (name / company / title / phones / email / address)
  • [ ] Receipt → merchant / date / total
  • [ ] Expo config plugin
  • [x] First npm release (0.1.0)

Documentation

Full guides live at https://g1d5ny.github.io/vision-camera-ocr-scanner/ (English) / https://g1d5ny.github.io/vision-camera-ocr-scanner/ko/ (한국어):

Contributing

License

MIT © jieonist


Made with create-react-native-library · powered by Nitro Modules and VisionCamera