Package Information
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 handlesDCTDecode(JPEG) andFlateDecodefilters. Scanned patents commonly useCCITTFaxDecodeorJBIG2Decodecompression, 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 afillRectmethod - none of
thedrawImage/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) andverify/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 callsgetDocument(). 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 resolvedpdfjs-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 viatesseract.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.