mac-ocr: Apple Vision OCR from the macOS Command Line
macOS CLI for OCR and searchable PDFs using Apple's Vision framework
At a glance
- What is it?
- mac-ocr wraps Apple's Vision framework in a Node-installable CLI that reads text from images and PDFs and writes searchable PDFs locally. The design is narrow by intent, and that narrowness is the main thing to evaluate.
- Who is it for?
- Adopt mac-ocr if you are on macOS and want OCR inside shell scripts, CI on Mac runners, or an agent workflow that should not pay vision tokens for document reading. Do not adopt it if you need Linux or Windows, if you need OCR quality guarantees on degraded scans, or if you need to tune the recognition engine itself, because the Vision framework is not configurable beyond the flags listed.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 27 days ago.
- What is it written in?
- Mainly Swift, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 18, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The gap mac-ocr fills between Preview, Shortcuts and a Python stack
macOS already has OCR. Live Text reads text out of images in Preview and in screenshots, and the Vision framework behind it is available to any Swift program. What is missing is a command you can put in a pipeline. mac-ocr is that command: a CLI that takes an image, a list of images, a PDF, a URL or stdin, and prints recognized text to stdout. The project describes itself as running entirely on the Mac with Apple's Vision framework, with nothing uploaded, which is the property that matters for receipts, contracts and internal scans.
The intended audience is narrower than "anyone who needs OCR". It is people who write shell scripts, who process batches of scans, or who run agents that would otherwise spend vision tokens reading documents. The README makes that last case explicit, and the repository ships a skills directory and a skills-npm prepare script so an agent can be told how to invoke the binary.
If you only ever OCR one screenshot a week, Preview and Live Text are already there and mac-ocr adds nothing. The value appears at the point where the input is a glob, the output needs to land in files, or the result needs to be machine-readable.
How mac-ocr works: Swift binary, Node wrapper, Vision underneath
The repository is a Swift package with a Node distribution layer. Package.swift and Sources/ hold the Swift implementation; package.json declares os: ["darwin"], engines.node >= 22, and a bin entry pointing at bin/mac-ocr. The build script compiles a universal binary for arm64 and x86_64 with swift build -c release, copies it into bin/, strips it, and then runs pkgroll to produce the JavaScript entry point. The npm package therefore ships a prebuilt binary, so the README states that no Xcode or Swift toolchain is needed on the user's machine.
Recognition itself is the default action, with no subcommand required. Output is plain text by default. With --format json you get bounding boxes, confidence values and page metadata; with --format jsonl the tool emits one JSON object per page as it goes. The README notes that PDF pages stream as they are recognized, so the first page's text appears before the rest of the document is finished.
A separate document subcommand uses the structured document recognizer introduced in macOS 26. It returns a transcript plus paragraphs, tables, lists, line geometry and table-cell ranges. The README is direct about the trade-off: document deliberately rejects --fast and --confidence, because the structured recognizer has no fast/accurate switch and dropping individual lines would make its aggregate text and structural containers inconsistent. That is a defensible design decision, and it also means the two subcommands are not interchangeable.
Installing mac-ocr and running a first real OCR pass
The README gives a single install command through npm. The package is marked darwin-only, so this will not install on Linux or Windows.
npm install -g mac-ocrTo try it without a global install, the README offers npx. The requirements line states macOS 10.15 or later, and because the package ships a prebuilt universal binary, no Swift toolchain is required.
npx mac-ocr receipt.jpgThe first real use is a plain image. Recognition is the default action, so there is no subcommand to type. Text goes to stdout.
mac-ocr receipt.jpgFor anything scripted, JSON output is the more useful form. Bounding boxes and confidence values come back per observation.
mac-ocr receipt.jpg --format jsonA multi-page PDF can be streamed as JSON Lines, one object per page, which is what you want when piping into another process.
mac-ocr scan.pdf --format jsonlTo save text next to each input rather than to stdout, -o accepts a file, a directory or a filename template. The README warns that templates must be quoted, because square brackets are a glob pattern in zsh.
mac-ocr ~/Screenshots/*.png -o '[dir]/[name].txt'Finally, the searchable PDF mode. Given a scan it writes scan.ocr.pdf next to the input by default; given an image it writes a one-page photo.ocr.pdf. The output looks identical to the source, but the text is selectable.
mac-ocr searchable-pdf scan.pdfMultiple inputs can be merged into one file. The README states that merged pages follow the exact argument order passed and that mac-ocr never sorts or reorders inputs.
mac-ocr searchable-pdf --merge -o lease.pdf page1.jpg page2.jpgPartitioned OCR and the small-label problem
The most interesting mechanism in mac-ocr is the OCR strategy for searchable PDFs. The default is --ocr-strategy auto, and the README explains why it exists: Vision can miss small labels when it analyzes a full high-resolution page at once, even when the same text is legible in a tighter crop. Auto mode starts with a full-page pass, then runs a partitioned pass only for large pages that contain small or missing text. The partitioning is recursive, splitting regions along their longer axis until text is large enough or the region falls below a calibrated size floor.
The README reports that in dogfooding on a high-resolution five-page scan, partitioned OCR recovered small form labels the full-page pass missed, while the generated PDF stayed around 7 MB. It also states the cost plainly: large partitioned runs may take longer because Vision processes regions serially.
Two constraints are worth internalizing before you rely on this. Auto mode skips partitioning when --roi is set, and forced partitioned mode cannot be combined with --roi. So if you are using a region of interest to limit recognition, you are on the standard path whether you intended that or not. The escape hatches are --ocr-strategy standard to opt out entirely and --ocr-strategy partitioned to force the partitioned pass on eligible pages.
There is a second, quieter behavior in non-merge mode: pages that already have selectable text are skipped, and a PDF that needs no OCR at all passes through unchanged. That is a sensible default and also a trap if your input is a hybrid scan where some pages carry a bogus text layer. --ocr-all-pages forces OCR on every page. The README points to docs/CLI.md for the details of what survives a rewrite and how "already has text" is decided; that documentation is where to look before trusting the skip behavior on production files.
Recognition flags, and what they cannot do
Both the default OCR action and searchable-pdf accept the same recognition options. --fast trades accuracy for speed. -l or --language takes a BCP-47 code and is repeatable, for example -l en-US -l ja-JP. -c or --confidence drops observations below a threshold between 0 and 1. -w adds custom vocabulary and is repeatable, with --custom-words-file reading one word per line. --no-language-correction turns off language correction. --min-text-height ignores text shorter than a fraction of image height. --pdf-dpi controls rasterization between 72 and 600 or auto. Encrypted PDFs take --password or the MAC_OCR_PDF_PASSWORD environment variable.
That list is the whole control surface, and it is the honest limit of the tool. You cannot swap in Tesseract, you cannot train on a font, and you cannot inspect why a particular line was dropped beyond the confidence score. Everything is Apple's recognizer. The flags let you steer around it, not replace it.
One more constraint sits in the PDF path: image inputs are sized from embedded DPI metadata when available, and images without usable DPI metadata fall back to 72 DPI, where 1px equals 1pt. A screenshot with no DPI tag will therefore be placed at a different physical size than you might expect. If the searchable output looks wrong in a viewer, that fallback is the first thing to check.
When mac-ocr is the wrong tool
The hard boundary is the platform. package.json declares os: ["darwin"] and the recognition engine is Apple's Vision framework, so there is no Linux or Windows path and no container story for a Linux CI runner. If your document pipeline is server-side, mac-ocr is not a candidate at all; you would be looking at Tesseract or a hosted OCR API instead.
The second boundary is macOS version. The document subcommand requires macOS 26 or later. On an older machine the subcommand is simply unavailable, and it is the only way to get the structured output with paragraphs, tables and cell ranges. If your fleet is mixed, that capability is not portable across it.
The third is quality control. Because the recognizer is fixed, there is no tuning knob for a bad scan beyond the flags above. Degraded faxes, handwriting and unusual scripts are outside what the exposed options can address. The README does not document rollback or a way to compare recognition runs, so validating an upgrade on your own corpus is on you.
Finally, the tool is a CLI, not a library in the sense of an OCR engine you embed. The package exports a JavaScript entry point, but the recognition logic lives in the Swift binary. If you need to call Vision directly from a Swift app, using the framework yourself is the more direct route.
mac-ocr compared with Tesseract and with calling Vision yourself
The obvious alternative is Tesseract. The difference in approach is where the recognition happens. Tesseract is an open source engine you install and tune, with language data files, page segmentation modes and configuration parameters you can adjust per document class. mac-ocr has none of that: it delegates to the Vision framework that ships with macOS, and its configuration surface is the flag table. Tesseract runs on Linux and Windows; mac-ocr does not. Tesseract can be tuned when it fails on a specific font or layout; mac-ocr cannot. The trade is that mac-ocr needs no model files, no training data and no per-language setup beyond a BCP-47 code, and it produces searchable PDFs with an invisible text layer in one command.
The second alternative is writing the Swift yourself against the Vision framework. That gives you full access to VNRecognizeTextRequest, its recognition level, its language settings and its revision, plus whatever post-processing you want. mac-ocr is a thin, opinionated wrapper over exactly that, and the wrapper adds the parts that are tedious to reimplement: glob expansion, -o templates, JSON and JSONL output, PDF rasterization, page-by-page streaming, the searchable-PDF writer and the partitioned strategy. If you need one of those behaviors to work differently, you are back to writing the Swift.
A third option, Live Text in Preview and Shortcuts, is fine for interactive use and has no scripting interface worth building a batch pipeline on.
Maintenance cost, licence and what to verify first
The repository is not archived, and the last push was on 2026-08-22, which is recent. Releases track closely: v1.0.0 on 2026-06-13, v1.1.0 on 2026-06-27, v1.1.1 on 2026-08-04. The project is MIT licensed, which permits commercial use and modification; the LICENSE file is at the repository root. Note that the npm package ships a prebuilt binary built from the Swift sources in the same repository, so a fork that changes the Swift code needs the build toolchain described in package.json, not just npm publish.
Upgrade cost is low on the surface, because npm install -g mac-ocr replaces the binary, and the package requires Node 22 or later. The real cost is behavioral. Partitioned OCR can change which text ends up in a searchable PDF, and the auto strategy decides per page whether to partition. A version bump can therefore change output on the same input without any flag change on your side. The README does not document a rollback procedure or a way to pin recognition behavior, so keeping a small corpus of representative documents and diffing the JSONL output between versions is the practical check.
The first things to verify on your own material: whether the default full-page pass catches the small text you care about, whether --ocr-strategy partitioned recovers it, and whether the DPI fallback of 72 affects the placement of any image input you feed to searchable-pdf.
Editorial conclusion
Adopt mac-ocr if you are on macOS and want OCR inside shell scripts, CI on Mac runners, or an agent workflow that should not pay vision tokens for document reading. Do not adopt it if you need Linux or Windows, if you need OCR quality guarantees on degraded scans, or if you need to tune the recognition engine itself, because the Vision framework is not configurable beyond the flags listed. Before committing, verify your target documents against the default full-page pass and against --ocr-strategy partitioned, and confirm the macOS version of every machine in the pipeline, since the document subcommand requires macOS 26 or later.
Frequently asked questions
Does Apple have built-in OCR?
Yes. mac-ocr does not implement OCR itself; it is a command-line wrapper around Apple's Vision framework, which the README describes as running entirely on the Mac with nothing uploaded. The tool exposes that framework as a CLI for images, PDFs, URLs and stdin.
What is the best OCR app for Mac?
The README does not compare mac-ocr with other OCR applications, so it makes no claim about being the best. What it does state is that mac-ocr is a command-line tool rather than a GUI app, that it installs with npm install -g mac-ocr, and that it requires macOS 10.15 or later.
How do I enable OCR?
There is nothing to enable. In mac-ocr, OCR is the default action, so running mac-ocr receipt.jpg prints recognized text to stdout without a subcommand. The only version gate in the README is that the document subcommand requires macOS 26 or later.
How can I extract text from an image on my Mac?
With mac-ocr you pass the image path and read stdout, or write it to a file with -o. The README shows mac-ocr photo.png for plain text and mac-ocr ~/Screenshots/*.png -o '[dir]/[name].txt' to write a .txt next to each image.
Community notes