An open API service indexing awesome lists of open source software.

https://github.com/rbayuokt/expo-mlkit-ocr

Production-ready Expo Module for on-device text recognition (OCR) using Google ML Kit Text Recognition v2 + OCRTextOverlay
https://github.com/rbayuokt/expo-mlkit-ocr

expo expo-mlkit expo-mlkit-ocr expo-ocr expo-text-recognition mlkit ocr react-native react-native-ocr text-recognition

Last synced: 9 days ago
JSON representation

Production-ready Expo Module for on-device text recognition (OCR) using Google ML Kit Text Recognition v2 + OCRTextOverlay

Awesome Lists containing this project

README

          

![expo-mlkit-ocr](docs/expo-mlkit-ocr.png)

# expo-mlkit-ocr

Production-ready Expo Module for on-device text recognition (OCR) using **Google ML Kit Text Recognition v2** for both iOS and Android.

## Preview


Android Preview
iOS Preview

## Features

- ✅ **On-device OCR** — no network requests required
- ✅ **ML Kit v2** — official Google ML Kit standalone (not Firebase ML Kit)
- ✅ **Structured output** — blocks, lines, and elements with bounding boxes
- ✅ **iOS & Android** — native implementations using Expo Modules API
- ✅ **TypeScript support** — fully typed API
- ✅ **Expo Config Plugin** — automatic native setup
- ✅ **Device support check** — `isSupported()` to detect device compatibility before OCR
- ✅ **Interactive overlay** — `` component for visualizing & selecting recognized text

## Installation

```bash
npx expo install expo-mlkit-ocr expo-image-picker expo-build-properties
```

> ⚠️ **`expo-build-properties` is REQUIRED** to set the iOS deployment target to `16.0` (needed by Google ML Kit on iOS).

## Setup

### 1. Configure `app.json` / `app.config.js`

Add **both plugins** to your config:

```json
{
"expo": {
"plugins": [
[
"expo-mlkit-ocr",
{
"iosEngine": "auto"
}
],
[
"expo-build-properties",
{
"ios": {
"deploymentTarget": "16.0"
}
}
]
]
}
}
```

> ⚠️ **Important:** Without `deploymentTarget: "16.0"` and `useFrameworks: "static"`, you will get:
> ```
> ERROR [runtime not ready]: Error: Cannot find native module 'ExpoMlkitOcr'
> ```

### 2. Build Your App

**With EAS Build:**

```bash
eas build --platform ios
eas build --platform android
```

**Or with local prebuild:**

```bash
npx expo prebuild --clean
npx expo run:ios
# or
npx expo run:android
```

> ❌ **Will NOT work in Expo Go** — this is a custom native module. You need a development client or EAS Build.

### Troubleshooting

**`Cannot find native module 'ExpoMlkitOcr'`**
- Did you add `expo-build-properties` plugin with `deploymentTarget: "16.0"`? ⚠️ Most common cause
- Did you run `npx expo prebuild --clean` after installing?
- Are you running on a development client (not Expo Go)?

**Build fails on iOS Simulator (arm64)**
- Use `iosEngine: "auto"` or `"vision"` in plugin config (uses Apple Vision instead of ML Kit)

## Usage

### Basic Example

```typescript
import { recognizeText } from 'expo-mlkit-ocr';
import * as ImagePicker from 'expo-image-picker';

async function pickAndRecognize() {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
});

if (result.canceled || !result.assets[0]) return;

try {
const recognition = await recognizeText(result.assets[0].uri);
console.log('Recognized text:', recognition.text);
console.log('Blocks:', recognition.blocks);
} catch (error) {
console.error('Recognition failed:', error);
}
}
```

### Output Format

The function returns a `RecognitionResult` object with this structure:

```typescript
export type RecognitionResult = {
text: string; // Full recognized text
blocks: TextBlock[];
};

export type TextBlock = {
text: string;
boundingBox: {
x: number;
y: number;
width: number;
height: number;
};
lines: TextLine[];
};

export type TextLine = {
text: string;
boundingBox: {
x: number;
y: number;
width: number;
height: number;
};
elements: TextElement[];
};

export type TextElement = {
text: string;
boundingBox: {
x: number;
y: number;
width: number;
height: number;
};
};
```

**Bounding box coordinates** are in the image's native coordinate system (top-left origin):
- `x`, `y` — top-left corner
- `width`, `height` — dimensions in pixels

### Example Output

```json
{
"text": "Hello World\nExample Text",
"blocks": [
{
"text": "Hello World",
"boundingBox": { "x": 100, "y": 50, "width": 200, "height": 50 },
"lines": [
{
"text": "Hello World",
"boundingBox": { "x": 100, "y": 50, "width": 200, "height": 50 },
"elements": [
{
"text": "Hello",
"boundingBox": { "x": 100, "y": 50, "width": 80, "height": 50 }
},
{
"text": "World",
"boundingBox": { "x": 190, "y": 50, "width": 110, "height": 50 }
}
]
}
]
}
]
}
```

## API Reference

### `recognizeText(uri: string): Promise`

Recognizes text from an image at the provided URI.

**Parameters:**
- `uri` (string) — file URI or content URI to the image (e.g., from `expo-image-picker`)

**Returns:**
- `Promise` — structured text recognition result

**Errors:**
- `INVALID_URI` — provided URI is not valid
- `IMAGE_LOAD_FAILED` — image could not be loaded from the URI
- `RECOGNITION_FAILED` — text recognition failed (rare)

### `isSupported(): boolean`

Checks if the device supports ML Kit text recognition (OCR).

Returns `true` if the device meets the minimum OS requirements; `false` otherwise. Call this before attempting `recognizeText()` to provide graceful fallback UI for unsupported devices.

**Minimum OS versions:**
- **iOS**: 16.0+
- **Android**: API 21+ (Android 5.0)

**Example:**

```typescript
import { isSupported, recognizeText } from 'expo-mlkit-ocr';

export default function App() {
if (!isSupported()) {
return OCR is not supported on this device.;
}

return (
{
const result = await recognizeText(imageUri);
console.log(result.text);
}}
/>
);
}
```

### `` — Interactive Bounding Box Overlay

Renders interactive bounding boxes over an image to visualize OCR results. Tap boxes to select text and trigger a callback (e.g., copy to clipboard).

**Usage:**

```typescript
import { recognizeText, OCRTextOverlay } from 'expo-mlkit-ocr';
import { Image, Clipboard } from 'react-native';
import { useState } from 'react';

export default function App() {
const [result, setResult] = useState(null);

async function pickAndRecognize() {
const picked = await ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'] });
if (!picked.assets[0]) return;

const asset = picked.assets[0];
const ocrResult = await recognizeText(asset.uri);
setResult(ocrResult);
}

return (
<>

{result && (
Clipboard.setString(item.text)}
>


)}
>
);
}
```

**Props:**

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `result` | `RecognitionResult` | — | OCR result from `recognizeText()` |
| `imageWidth` | `number` | — | Native image width (pixels) |
| `imageHeight` | `number` | — | Native image height (pixels) |
| `children` | `ReactNode` | — | Image component to wrap |
| `highlightLevel` | `'block' \| 'line' \| 'element'` | `'line'` | Which level to highlight |
| `resizeMode` | `'contain' \| 'cover'` | `'contain'` | Image resize behavior |
| `boxColor` | `string` | `'#00BFFF'` | Box border & fill color (hex) |
| `selectedBoxColor` | `string` | `'#FF6347'` | Color when a box is selected |
| `boxOpacity` | `number` | `0.25` | Fill opacity (0–1) |
| `strokeWidth` | `number` | `2` | Border width (pixels) |
| `cornerRadius` | `number` | `4` | Rounded corner radius (pixels) |
| `multiSelect` | `boolean` | `true` | Allow selecting multiple boxes |
| `onSelect` | `(item) => void` | — | Callback when box(es) are tapped (single item or array) |
| `style` | `ViewStyle` | — | Optional wrapper style |

**Multi-Select Example:**

```typescript
// Single selection (one box at a time)
console.log('Selected:', item.text)}
>

// Multiple selection (tap multiple boxes)
{
if (Array.isArray(items)) {
console.log('Selected items:', items.map(i => i.text).join(', '));
} else {
console.log('Single item:', items.text);
}
}}
>

```

**Highlights:**
- ✅ Pure React Native (no external canvas library)
- ✅ Tap to toggle selection + visual highlight
- ✅ Multi-select mode: tap multiple boxes, all stay highlighted
- ✅ Single-select mode: only one box highlighted at a time
- ✅ Works with any `` component (react-native, expo-image, etc.)
- ✅ Automatic coordinate scaling for `contain` and `cover` resize modes
- ✅ Full TypeScript support

## Common Errors

### `Error: expo-mlkit-ocr is not supported on web.`

The module only works on iOS and Android. For web support, use a third-party OCR service (e.g., Tesseract.js).

```typescript
import { Platform } from 'react-native';
import { recognizeText } from 'expo-mlkit-ocr';

if (Platform.OS !== 'web') {
const result = await recognizeText(uri);
} else {
// Use a web-based OCR service
}
```

### `IMAGE_LOAD_FAILED`

- Ensure the URI is valid and the file exists
- Use URIs from `expo-image-picker` or `expo-camera` which are guaranteed to work
- On Android, both `file://` and `content://` URIs are supported

### iOS Simulator (arm64) + iOS 26.x

Google ML Kit CocoaPods binaries exclude the `ios-arm64-simulator` slice, so arm64-only simulator runtimes (for example iOS 26.x on Apple Silicon) can’t link ML Kit frameworks and will fail at build/link time.

To keep development on simulator unblocked, `expo-mlkit-ocr` supports **Apple Vision** as a fallback OCR engine on iOS. The **JavaScript response shape stays the same** (`text`, `blocks`, `lines`, `elements`, `boundingBox`); only the underlying OCR engine changes.

```json
{
"expo": {
"plugins": [
["expo-mlkit-ocr", { "iosEngine": "vision" }]
]
}
}
```

Supported `iosEngine` values:
- `"auto"` (default): use Apple Vision (simulator-friendly default)
- `"mlkit"`: use Google ML Kit (won’t build on arm64-only iOS Simulator runtimes)
- `"vision"`: always use Apple Vision (disables ML Kit pods)

Legacy option (equivalent to `iosEngine: "vision"`):
- `disableMlkitOnSimulator: true`

## Development

### Project Structure

```
expo-mlkit-ocr/
├── src/ # TypeScript source
│ ├── index.ts
│ ├── ExpoMlkitOcr.types.ts
│ ├── ExpoMlkitOcrModule.ts
│ ├── ExpoMlkitOcrModule.web.ts
│ └── OCRTextOverlay.tsx # Interactive overlay component
├── ios/
│ ├── ExpoMlkitOcrModule.swift # ML Kit integration
│ └── ExpoMlkitOcr.podspec
├── android/
│ ├── build.gradle
│ └── src/main/java/.../ExpoMlkitOcrModule.kt
├── app.plugin.js # Expo config plugin entry
├── plugins/
│ └── withMlkitSimulatorArm64Fix.js
├── example/ # Example app
│ ├── App.tsx
│ └── app.json
└── expo-module.config.json
```

### Building from Source

```bash
# Install dependencies
npm install

# Build the module (src/ → build/)
npm run prepare

# Open the example app (iOS)
npm run open:ios

# Or run the example app with Expo CLI
cd example
npx expo start

# Scan QR code with Expo Go or run on device/simulator
```

### Running the Example App

The example app at `example/` demonstrates:
1. Picking an image from the device library
2. Running OCR with `recognizeText()`
3. Displaying the results (full text, blocks, lines, elements)

## License

MIT

## Contributing

Contributions are welcome! Please ensure:
- TypeScript code compiles without errors
- Native code follows platform conventions
- Example app works on both iOS and Android

## References

- [ML Kit Text Recognition (v2) — iOS](https://developers.google.com/ml-kit/vision/text-recognition/v2/ios)
- [ML Kit Text Recognition (v2) — Android](https://developers.google.com/ml-kit/vision/text-recognition/v2/android)
- [Expo Modules API](https://docs.expo.dev/modules/get-started/)

Made with ❤️ by [rbayuokt](https://github.com/rbayuokt)