Skip to main content
Run optical character recognition on any image using the device’s native on-device text engine, returning the extracted text straight to your web app. Read from a hosted image, a photo the user picks, a multi-page document scan, or raw image bytes already held in memory. Recognition runs entirely on-device, so it works offline and never sends image data anywhere. Useful for receipts, invoices, business cards, ID capture, handwritten notes, and any flow where you would otherwise ship an image to a cloud OCR service.
Assign window.onVisionEvent before issuing the first call. Results are delivered to that callback as soon as recognition finishes, and any event emitted before the callback exists is dropped rather than queued.

Installation


How it works

OCR is a two-part flow. Assign window.onVisionEvent to receive results, then call vision://ocr with the image you want recognized. Each call fires a queued event the moment it is accepted, then a success event carrying the extracted text once recognition completes, or an error event if it fails. Every event echoes back the id you passed, so a single callback can route results across many requests running at once.
The result text is normalized before delivery: every line is trimmed of leading and trailing whitespace, consecutive blank lines collapse to a single break, and the whole string is stripped at both ends. The lines array is left as the engine produced it, so each line object holds the raw recognized text if you need it. evt.text is ready to display or parse without further cleanup.

Reading the result

Assign window.onVisionEvent once. It receives every event for every request, each tagged with the id you supplied and a status describing what happened.
A successful result carries the full text and a per-line breakdown. A data: URI or hosted receipt produces a success event shaped like this:
On Android the recognizer does not expose a per-line score, so confidence is absent. The same receipt produces:
A failure carries a stable code and an advisory message:
The four statuses:
object
The request was accepted and recognition is running. Carries only type and id.
object
Recognition completed. Carries text, the full extracted string with lines joined by \n, and lines, an array of { text, confidence? } objects in reading order. confidence is a float from 0 to 1 on iOS; it is omitted on Android, where the recognizer does not expose a per-line score. Treat a missing confidence as unknown rather than zero.
object
Recognition failed. Carries error.code, a stable machine-readable string you can branch on, and error.message, a human-readable detail for logging. See Error reference for the full list.
object
The user closed a picker or the document scanner without selecting anything. No text was produced. Carries only type and id.

Recognizing a hosted image

Pass an HTTPS URL as src to recognize an image already hosted on your CDN or storage. The native side fetches it with the WebView’s cookies and user-agent attached, so images behind your app’s own session are reachable without extra authentication.
src must be a publicly reachable HTTPS URL. Data URLs, blob URLs, and file:// paths are not fetched over the network and will not resolve. If your app produces an image on the client, a canvas export or a processed photo, upload it to your storage layer first and pass the returned HTTPS URL. To recognize in-memory bytes directly without an upload, use a data URI instead, covered below.

Letting the user choose an image

Three picker tokens open a native chooser instead of taking a URL. Pass one as src and the user’s selection flows straight into recognition. @imagepicker opens the system photo library. @filepicker opens a file browser filtered to images. Both let the user pick an existing image; the difference is purely which native chooser appears.
Closing a picker without choosing fires a dismissed event on that request’s id. Pickers are modal, so only one can be open at a time; a second picker request issued while one is already open returns picker_busy immediately while the first stays on screen.

Scanning a multi-page document

@documentscanner opens the native document camera with automatic edge detection and perspective correction. The user captures one or more pages, confirms the batch, and every page is recognized together and returned in a single success event. The pages are concatenated in capture order into evt.text, separated by line breaks like any other text, so you can render or parse the whole document as one string.
A two-page scan arrives as one success event, every page’s lines flattened into a single lines array and joined into text:
If the device has no document scanner available, the request fails with scanner_unsupported. If the scanner opens but errors before recognition starts, it fails with scanner_failed. Closing the scanner without confirming any pages fires dismissed.

Recognizing an in-memory image

When the image already exists in the page as bytes, a canvas export, a generated graphic, a freshly decoded blob, pass it inline as a data URI and skip the upload entirely. A bare base64 string is also accepted as a fallback, though a data URI is preferred because it declares the image type.
In-memory images are recognized on the same parallel path as hosted URLs, so they run alongside any other in-flight jobs without blocking.

Choosing a recognition language

Both platforms auto-detect the script by default, so most apps never set lang. Pass it only when you already know the script and want to constrain recognition, which improves accuracy for non-Latin text. On Android the hint selects the recognizer for Latin, Chinese, Japanese, Korean, or Devanagari script; on iOS it narrows the candidate languages the engine considers.
The value is a BCP-47 tag or a comma-separated list of them. Omit it for English and other Latin-script content.

Running several jobs at once

Recognition jobs are independent. Issue as many vision://ocr calls as you need with distinct id values, and each result arrives on the callback as it finishes. Results come back in completion order, not the order you submitted them, so always key off evt.id rather than assuming a sequence.
Hosted, in-memory, and local-file jobs all run in parallel. The picker tokens are the one exception: because they present a modal chooser, only one picker job can be active at a time.

Error reference

Every failure arrives as an error event with a stable code. Messages are advisory and may change; branch on the code.

Resources

NPM Package

despia-native