Scribe.js OCR, tested on scanned PDFs in Node
1,895
of 1,955 words, default settings
OCR a scanned PDF in Node
15 s for 10 pages, 2 workers
23 MB language data, first run
In a folder with scan.pdf:
npm i scribe.js-ocr@0.18.2
node scribe-ocr.mjs scan.pdf > scan.txt
scribe-ocr.mjs:
// scribe-ocr.mjs: OCR a scanned PDF or image with Scribe.js.
// node scribe-ocr.mjs scan.pdf > scan.txt
import scribe from "scribe.js-ocr";
scribe.opt.workerN = 2; // the default is up to 8 in Node
const text = await scribe.extractText([process.argv[2]]);
console.log(text);
await scribe.terminate();
On a PDF with a typed cover page, it kept all 13 cover words too.
scribe extract gives 0 words
| Command | Words | Exit code |
|---|---|---|
scribe extract scan.pdf out.txt |
0 | 0 |
scribe recognize scan.pdf --output out -w 2 |
1,882 | 0 |
extract only reads text that is already in the PDF. A
scan has none, so out.txt is empty. recognize runs OCR
and writes a searchable copy to out/scan.pdf.
Reading text back from out/scan.pdf
scribe extract read 1,882 words. pdftotext read 1,776,
because it split words such as "f or". With
pdftotext -raw it read 1,870.
Scribe.js vs tesseract.js
| Engine | Words | Time |
|---|---|---|
| Scribe.js, default | 1,895 | 14 s |
Scribe.js, mode: "speed" |
1,880 | 8 s |
| tesseract.js 7.0.0 | 1,876 | 18 s |
The default found 15 more words than speed mode, and took about 70% longer. tesseract.js does not read PDFs, so it got the 10 page JPEGs.
The first run needs the internet
Scribe.js downloads eng.traineddata from cdn.jsdelivr.net and saves it in the folder you run from. With no network and no saved file, it stopped:
TypeError: fetch failed
With the file in the folder, the same run with no network found 1,894 words. Run from that folder, or copy the file.
AGPL: read this before you ship it
Scribe.js is AGPL 3.0. Its own docs say that to use it in your program, front end or server, you must publish your program under AGPL 3.0 or a compatible license, or buy a license.
tesseract.js is Apache 2.0.
Need text with nothing to install?
Our Actor takes PDF, JPG and PNG files and returns Markdown, from one web request. On the same 10 pages, inside a PDF, it found 1,874 words. Scribe.js found more.
curl -s -X POST "https://api.apify.com/v2/acts/brasspage~pdf-ocr-to-markdown/run-sync-get-dataset-items" \
-H "Authorization: Bearer $APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"documentUrls": ["https://api.apify.com/v2/key-value-stores/sTnmNrGoSsi7tLQvs/records/anythingllm-mixed-11-pages.pdf"]}' \
| jq -r '.[].markdown' > book.md
It costs $0.006 a page.
How I tested it
- Pages: the 10 scans of OCR in n8n, proofread Wikisource book pages, 1,955 true words. Each page is one 300 ppi JPEG.
- scribe.js-ocr 0.18.2 and tesseract.js 7.0.0 from npm, Node 22, on an Apple Silicon Mac. 2 workers in every run, at lowest priority.
- Times: 2 rounds with the language data saved. The rounds matched within half a second. The first-run time includes the download.
- Score: true words found in order, with the scorer of the tessdata test. 7 default runs found 1,894 or 1,895.
- No network: macOS sandbox-exec with all network access denied.
- The Actor: one real run, build 0.1.65, on November 12. Its file holds the same 10 pages plus a typed cover page.
What I did not test
- Scribe.js in the browser, and its paid cloud OCR options.
- Windows and Linux.
- Phone photos, handwriting, and languages other than English.
Sources
Two ways to skip the work
Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.
Questions people ask
Does Scribe.js work on scanned PDFs?
Yes, with extractText in Node or the recognize command. On 10 scanned pages, extractText found 1,895 of 1,955 words. The scribe extract command does no OCR: it gave 0 words, with exit code 0.
Is Scribe.js more accurate than tesseract.js?
A little, in this test. On the same 10 pages, Scribe.js found 1,895 words and tesseract.js 7.0.0 found 1,876. Scribe.js speed mode found 1,880.
Can Scribe.js run offline?
After the first run. It downloads eng.traineddata (23 MB) and saves it in the folder you run from. With no network and no saved file, it stopped with TypeError: fetch failed.
Related guides
- keras-ocr in 2026: the install fix, tested vs Tesseract
- tesseract.js accuracy vs the Tesseract command, tested
- Tesseract OCR in Node.js, tested on 55 scans
- How to make a PDF searchable: scanned PDFs, tested
- MarkItDown OCR: scanned PDFs, the plugin and Ollama
- JPG to searchable PDF, free: 5 ways tested on a Mac