pdf-tesseract-ocr

Renders scanned PDF pages to real bitmaps via pdf.js and OCRs them with tesseract.js - works regardless of the PDF's internal image compression (CCITT, JBIG2, JPEG2000, etc.), unlike nodes that try to extract embedded image objects directly.

Documentation

n8n-nodes-pdf-tesseract-ocr

An n8n community node for OCR-ing scanned PDFs that have no extractable text
layer. Renders each page to a real bitmap using pdf.js
(via its built-in Node canvas factory, backed by @napi-rs/canvas), then
OCRs each rendered page with tesseract.js.

Why this exists

Two existing community nodes were evaluated first and rejected:

  • n8n-nodes-tesseractjs: extracts embedded image objects directly
    from the PDF's internal structure, but only handles DCTDecode (JPEG) and
    FlateDecode filters. Scanned patents commonly use CCITTFaxDecode or
    JBIG2Decode compression, which it silently skips - producing empty
    output on exactly the documents that need OCR.
  • n8n-nodes-pdf-ocr: correct approach (renders pages via pdf.js) but
    implements its own fake canvas that only has a fillRect method - none of
    the drawImage/text/path-drawing calls pdf.js's renderer actually needs.
    In practice this renders a blank white page, so Tesseract has nothing to
    OCR regardless of the PDF's content. Also unmaintained (all versions
    published in a single 2-hour window in July 2025, source repo now 404s).

This node takes the same rasterize-then-OCR approach, but uses pdf.js's own
built-in Node canvas support (pdfDocument.canvasFactory), which is backed
by @napi-rs/canvas - a real, actively maintained canvas implementation
(66M+ downloads/month) - not a hand-rolled stub. Because it rasterizes the
full page rather than trying to decode specific embedded image filters, it's
agnostic to the PDF's internal image compression.

Verified against two real scanned US patents with CCITT/JBIG2-encoded page
images that return zero characters from normal text-layer extraction
(US4429606.pdf, rubriks.pdf in verify/) - both produce full, legible
extracted text. See verify/test.mjs (standalone pipeline test) and
verify/test-node.mjs (tests the actual compiled node via a simulated n8n
execution context).

pdfjs-dist version is pinned to match n8n-core

pdfjs-dist is pinned to an exact version (5.3.31 as of this writing) that
matches whatever n8n's own built-in "Extract from File" node bundles
(packages/nodes-base/package.json declares "pdfjs-dist": "catalog:"),
not the latest available version. This isn't a preference - it's
required for correctness.

n8n's own PDF node loads its own pdfjs-dist copy in the same long-running
n8n process. pdf.js permanently caches which "worker" implementation it
loaded, for the lifetime of the process, the first time anything calls
getDocument(). Whichever of the two copies - n8n-core's or this node's -
runs first in a freshly-restarted process "wins" that cache, and the other
one then throws The API version "X" does not match the Worker version "Y".
This happens even though the two are genuinely separate npm installs in
separate node_modules trees; the collision is inside pdf.js's own runtime
state, not an installation problem. Pinning both to the identical version
makes the shared cache harmless instead of requiring build-time isolation
(a separate worker_thread was considered and works, but adds real
complexity for no benefit once the versions just match).

Verified empirically (not assumed) that 5.3.31 has no functional gap versus
newer versions for this use case: both US4429606.pdf and rubriks.pdf
(CCITT/JBIG2-encoded) produce identical extracted text under 5.3.31 and
6.2.108.

If you bump n8n to a version that changes its own bundled pdfjs-dist,
update this pin to match - check the running container:

cat ~/.n8n/nodes/node_modules/n8n-nodes-pdf-tesseract-ocr/node_modules/pdfjs-dist/package.json | grep version

or check n8n's own packages/nodes-base/package.json for its resolved
pdfjs-dist version.

Node

PDF Tesseract OCR (pdfTesseractOcr)

Parameter Description
Input Binary Field Name of the binary property holding the PDF (default data)
Language Tesseract language code(s), e.g. eng or eng+fra
Render Scale Page rasterization scale factor before OCR (default 2)
Max Parallel Pages Pages OCR'd concurrently, one Tesseract worker per slot (default 4)

Output: one item per input item, { text, totalPages, pages: [{pageNumber, text}] }.

Performance: pages are OCR'd in parallel, not one at a time

Pages are independent, so rendering happens sequentially (fast, and caps how
many full-resolution bitmaps sit in memory at once) while OCR - the actually
slow part - is distributed across a pool of Tesseract workers via
tesseract.js's createScheduler(). A 9-page scanned patent went from 111s
(single worker) to 19s (4 workers) in testing.

Max Parallel Pages is capped by real CPU cores available, and should leave
headroom for n8n itself and any other concurrent workflows on the same
machine - don't set it to your full core count. A large scanned document
(100+ pages) can still take several minutes even in parallel; pair this with
a generous maxDuration on whatever's calling the webhook chain that leads
here, or expect timeouts on very large documents.

Build

npm install
npm run build

Install into a self-hosted n8n instance

Either publish to npm and install via Settings → Community Nodes in the n8n
UI, or install manually without publishing (works for Docker deployments,
also the only option for a private/unpublished package):

docker exec -it n8n sh
mkdir -p ~/.n8n/nodes && cd ~/.n8n/nodes
npm i n8n-nodes-pdf-tesseract-ocr   # or: npm i /path/to/packed/tarball.tgz

Then restart the n8n container. Make sure ~/.n8n is on a persistent
volume, not the container's writable layer, or the install won't survive a
container recreation.

Discussion