Tools,

tesseract.js reads as well as Tesseract. Two defaults differ.

1

word apart, of 14,446, same model and mode

For printed scans: $0.006 a page

Same 55 book pages, same accuracy

Words found, of 14,446
Command, as installed
14,012
tesseract.js, as installed
14,017
tesseract.js, set like the command
14,011

Set like the command, 15 of 55 pages came out the same, letter for letter. Each program won some of the rest.

How I tested it
  • Tesseract 5.5.4 from Homebrew, and tesseract.js 7.0.0 in Node 22, on one Mac. One page at a time, one thread.
  • Scanned book pages from Wikisource, each proofread by two people, picked at random with a fixed seed. Five more were a handwritten manuscript, so I left them out.
  • A word counts as found when the proofread page has it and the output has it too, page by page. Curly quotes count as straight ones.
  • Set like the command: Homebrew's eng file and mode 3. No --dpi: both programs guessed the resolution.

Two defaults differ from the command

Setting tesseract.js 7 tesseract command
Page mode 6, one block of text 3, automatic
Model best_int, 5.2 MB, downloaded Homebrew's eng: fast, 4.1 MB
Engine 5.1.0-288, WebAssembly 5.5.4

The Tesseract library starts in mode 6. The command switches to mode 3 itself, and tesseract.js calls the library directly. As installed, its text matched mode 6 on all 75 test pages.

Set the mode in one line

PSM.AUTO is mode 3, what the command and pytesseract use.

import { createWorker, PSM } from "tesseract.js";

const worker = await createWorker("eng");
await worker.setParameters({ tessedit_pageseg_mode: PSM.AUTO });
const { data } = await worker.recognize("page.jpg");
console.log(data.text);
await worker.terminate();

For speed, swap the createWorker line for this one. It loads the command's smaller model:

const worker = await createWorker("eng", 1, {
  langPath: "/opt/homebrew/share/tessdata",
  gzip: false,
  cacheMethod: "none",
});

Keep cacheMethod: "none". tesseract.js saves eng.traineddata in the working folder and reads it first next time. Without the line, my swap did nothing.

As installed, 1.7 times slower than the command

Seconds a page, median, 55 book scans
Command, one call a page
0.81
tesseract.js, set like the command
0.98
tesseract.js, as installed
1.34

The command time includes starting the program for each page, as pytesseract does. tesseract.js loaded its model once, in 0.21 s.

Receipts: mode 6 finds more, and invents more

20 receipt photos from the CORD set, with 509 words in all.

Setting Words found Words written
Command, as installed 176 338
tesseract.js, as installed 285 1,264
tesseract.js, mode 3 181 326

Every setting got more than half the letters wrong on these photos. For receipts, see receipt OCR in Python.

Limits, and what I did not test
  • Browsers. I ran tesseract.js in Node only.
  • Other languages, the legacy engine, and Windows.
  • Thai: tesseract.js read 82.8% of the letters on 82 scans. Thai is tested here.
  • Other machines. Speed is from one Mac.
  • Handwriting: the five manuscript pages are not in the totals.
Sources

Our Actor runs the Tesseract program

Our Apify Actor runs the Tesseract program itself, not tesseract.js, with the fast model. It turns each page upright before it reads it.

Two ways to skip the work

Run it yourself. Our Apify Actor turns scanned PDFs into Markdown, tables included.

Try the Actor

Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First page free.

Get one page free

Questions people ask

Is tesseract.js less accurate than Tesseract?

Not in a way that matters. Set like the command, it found 14,011 of 14,446 words on 55 book scans, and the command found 14,012.

What page mode does tesseract.js use by default?

Mode 6, one block of text. The tesseract command uses mode 3. Set tessedit_pageseg_mode to PSM.AUTO for mode 3.

Can tesseract.js use my own traineddata file?

Yes. In Node, set langPath to its folder, gzip to false and cacheMethod to none. Without the last one, a cached eng.traineddata can win.