Get your API key
How to OCR a PDF and make scanned pages searchable (2026 guide)

How to OCR a PDF and make scanned pages searchable (2026 guide)

OCR a PDF to make scanned pages searchable: detect image-only pages, run OCRmyPDF or Tesseract, tune DPI and language packs, and batch process a whole folder.

11 min read

OCR turns a scanned PDF into a searchable one by recognizing the characters in the page image and writing them back as an invisible text layer behind the scan. The page looks identical, but the words become selectable, copyable, indexable, and readable by screen readers. The shortest working path on any platform is one command: ocrmypdf --deskew --language eng input.pdf output.pdf. Everything below explains when you need it, which engine to pick, how to stop the output from being garbage, and how to avoid the whole problem when you control how the document is produced.

What does OCR actually do to a PDF?

OCR (optical character recognition) is the process of converting an image of text into machine-readable characters. Applied to a PDF, it does not replace the page. It adds a second, invisible layer.

A scanned PDF is a container holding one raster image per page, usually JPEG or CCITT Group 4. There is no text in it, only pixels. When you press Cmd+F and search, the viewer finds nothing, because there is nothing to find.

An OCR pass rasterizes each page, runs a recognition engine over the pixels, and writes the recognized words back into the PDF at the coordinates where they were found, using PDF text rendering mode 3 (invisible). The visible page is byte-for-byte the same scan. The text sits behind it, aligned word by word. Select a line in a viewer and the highlight lands on the right pixels.

This matters beyond convenience. A PDF without a text layer fails accessibility audits under the European Accessibility Act, cannot be indexed by search or e-discovery systems, and cannot be parsed by any downstream tool. For a longer treatment of the text-extraction side, see how to extract text from a PDF.

How do I know if a PDF needs OCR?

Run pdftotext document.pdf - and read the output. Empty output means no text layer, which means the file needs OCR. Text output means the layer already exists and running OCR would add a duplicate, misaligned layer on top of good data.

pdftotext ships with Poppler and is available as poppler-utils on Debian and Ubuntu and poppler on Homebrew.

# no output at all -> image-only PDF, needs OCR
pdftotext scan.pdf - | head -20
 
# count characters per page to catch partially scanned files
pdftotext scan.pdf - | wc -c

A count under roughly 100 characters for a full page of body copy usually means the only text present is a header or a stamped page number, and the body is still an image. Mixed files like this are common: a born-digital contract with three scanned exhibits appended. OCRmyPDF handles them with --skip-text, which leaves existing text pages alone.

If you want a quick visual check without a terminal, drop the file into the free PDF to text toolTry it free. It runs pdf.js in the browser, extracts whatever text layer exists, and returns nothing for an image-only scan. No upload, no signup.

Which OCR tool should I use for PDFs?

Use OCRmyPDF for whole PDF files, Tesseract directly only if you need per-page control, and a cloud OCR service when you need table structure or handwriting.

ToolTypeBest forHandles PDF directlyCost
OCRmyPDFCLI + Python libWhole-file OCR, archival PDF/A outputYesFree (MPL-2.0)
TesseractOCR enginePage images, custom pipelinesNo, needs rasterizing firstFree (Apache-2.0)
PaddleOCROCR toolkitDense layouts, CJK scripts, tablesNoFree (Apache-2.0)
Google Document AICloud APIForms, tables, handwritingYesPer page
AWS TextractCloud APIForms, key-value pairs, tablesYesPer page

OCRmyPDF is a wrapper, not an engine. It calls Tesseract for recognition and Ghostscript for the PDF rebuild, and adds the parts that make the result usable: page rasterization, deskewing, image optimization, PDF/A conversion, and the invisible text layer placement. Doing all of that by hand is the reason "just use Tesseract" turns into a weekend.

The cloud services are a different product. Tesseract returns words and their bounding boxes. Document AI and Textract return structured fields, table cells, and confidence scores. If your goal is to pull line items out of scanned invoices, that structure is what you are paying for. If your goal is a searchable archive, it is wasted money.

How do I OCR a PDF with OCRmyPDF?

Install OCRmyPDF, then run it with the input and output paths. It refuses to overwrite a PDF that already has text unless you tell it what to do, which is a safety feature, not a bug.

# macOS
brew install ocrmypdf
 
# Debian / Ubuntu
sudo apt install ocrmypdf tesseract-ocr-eng
 
# Any platform with Python
pip install ocrmypdf   # requires tesseract + ghostscript on PATH

Then:

# the common case
ocrmypdf --deskew --language eng input.pdf output.pdf
 
# mixed file: some pages already have text
ocrmypdf --skip-text input.pdf output.pdf
 
# force re-OCR, discarding any existing (bad) text layer
ocrmypdf --redo-ocr input.pdf output.pdf
 
# French and English in the same document
ocrmypdf --language fra+eng input.pdf output.pdf
 
# plain PDF instead of the default PDF/A-2b
ocrmypdf --output-type pdf input.pdf output.pdf

Three flags cover almost every real file. --skip-text for mixed documents, --redo-ocr when a previous tool wrote a bad layer, and --force-ocr when you want to rasterize everything and start clean, accepting that vector text becomes an image.

OCRmyPDF emits PDF/A-2b by default, which is the archival subset most retention policies require. That is a useful default for scanned records. The PDF/A compliance guide covers what the standard actually constrains.

How do I OCR a PDF in Python?

Call ocrmypdf.ocr() for the file-level path, or combine pdf2image and pytesseract when you need the recognized text per page rather than a rebuilt PDF.

import ocrmypdf
 
ocrmypdf.ocr(
    "scan.pdf",
    "searchable.pdf",
    language="eng",
    deskew=True,
    skip_text=True,       # leave pages that already have text
    optimize=1,           # lossless image optimization
    progress_bar=False,
)

The third tab is what OCRmyPDF does internally, minus deskewing, image optimization, PDF/A conversion, metadata preservation, and bookmark handling. Reproducing those is the actual work. The 12-line version is fine for a one-off script and a poor foundation for a service.

For the adjacent problem of turning a PDF into an editable document rather than a searchable one, convert PDF to Word in Python covers pdf2docx and the layout-preservation tradeoffs.

How do I improve OCR accuracy on scanned PDFs?

Fix the input, not the engine. Resolution, skew, and language selection account for most bad output, and all three are cheaper to correct than switching OCR engines.

Scan at 300 DPI. The Tesseract quality guide states that images need at least 300 DPI for reliable results, and that Tesseract internally upscales anything below that. Pushing past 400 DPI mostly buys processing time. Small print, footnotes, and 6pt legal boilerplate are the exception and benefit from 400 to 600 DPI.

Deskew before recognition. A page rotated by two degrees produces noticeably worse character segmentation than a straight one. ocrmypdf --deskew corrects the rotation on the rasterized copy. --rotate-pages fixes pages that are sideways or upside down, using the recognition confidence to pick the orientation.

Set the language explicitly. Tesseract defaults to English. Running an English model over a French document mangles every accented character, and the failure is silent. Use -l fra, or -l deu+eng for a document that mixes both. Install the matching tesseract-ocr-<lang> package first, otherwise Tesseract exits with a missing traineddata error.

Clean the image when the source is a fax or a photocopy. --clean runs unpaper to remove speckle and border artifacts before recognition, without changing the image that ends up in the output PDF.

What does not help: raising DPI on an already-blurry source, running OCR twice, or asking Tesseract to read handwriting. Tesseract does not recognize cursive script, and no flag changes that.

How do I OCR a folder of PDFs in batch?

Loop with GNU parallel and pass --skip-text so already-processed files are cheap no-ops. OCR is CPU-bound and embarrassingly parallel, so one job per core is the right default.

mkdir -p ocr
find . -maxdepth 1 -name '*.pdf' \
  | parallel --bar -j "$(nproc)" \
      'ocrmypdf --skip-text --deskew --language eng {} ocr/{/}'

--skip-text makes the pipeline idempotent. Re-running it over a folder where half the files were done yesterday costs a parse of each finished file rather than a full re-recognition. {/} strips the directory from the input path so the output lands flat in ocr/.

Expect roughly one to five seconds per page per core on 300 DPI scans of normal body text, with dense pages, high resolution, and multi-language models at the slow end. A 10,000-page archive on 8 cores is therefore a lunch break, not an afternoon. Budget disk too: --output-type pdfa with --optimize 1 typically lands close to the input size, while --force-ocr on a vector PDF can multiply it, because every page becomes a raster image. If the result is heavier than you want, the free compress PDF toolTry it free runs the same downsampling logic in the browser.

When does self-hosted OCR stop being worth it?

Self-hosted OCR works until it becomes a service. The break happens when OCR moves from a script you run to an endpoint other people call.

The specifics, in the order teams hit them:

  • Image weight. Tesseract, Ghostscript, unpaper, and the language packs add roughly 300 MB to a Docker image. Every language pack is another 10 to 30 MB.
  • CPU cost. OCR pins a core for seconds per page. A queue of 500-page scans starves whatever else shares that box, so OCR needs its own worker pool and its own autoscaling policy.
  • Serverless limits. AWS Lambda caps at 15 minutes and 10 GB of ephemeral storage. A large scanned archive exceeds the timeout on a single invocation, so you end up building page-level fan-out and result reassembly.
  • Failure modes. Corrupt scans, encrypted files, and pages with zero recognizable content all fail differently. Each one needs a distinct retry and dead-letter path.

None of this is hard. All of it is operational surface that has nothing to do with the product you were building.

How do you avoid needing OCR at all?

Do not scan documents you generated. A PDF rendered from HTML by a browser engine already contains a real text layer, correct reading order, and embedded fonts, so it is searchable and accessible the moment it exists.

This sounds obvious and is routinely violated. Invoices get rendered to PDF, printed, signed, scanned, and then OCR'd back into text that was already there twenty minutes earlier. Every step of that round trip loses fidelity and adds cost.

OCR is the right answer for documents that arrive as pixels: paper archives, faxes, third-party scans, photographed receipts. It is the wrong answer for anything your own system produces.

PDF4.dev renders HTML templates to PDF through headless Chromium, so the output carries the same text layer a browser would produce when printing. Handlebars variables fill the template, the API returns the file, and no recognition step exists anywhere in the pipeline.

curl -X POST https://pdf4.dev/api/v1/render \
  -H "Authorization: Bearer p4_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "template_id": "invoice",
    "data": { "invoice_number": "INV-001", "total": "$1,500.00" },
    "delivery": "url"
  }'

The result is text-native, selectable, and indexable, and it can be tagged for accessibility. The companion guide on accessible tagged PDFs from HTML covers the tagging side.

Generating documents rather than digitizing them removes the OCR step, the accuracy tuning, and the 300 MB of OCR dependencies. Get your API key and render your first text-native PDF in a few minutes.

Summary

OCR a scanned PDF with ocrmypdf --deskew --language eng input.pdf output.pdf. Confirm the file needs it first with pdftotext file.pdf -, scan or rasterize at 300 DPI, set the correct language pack, and use --skip-text so batch jobs stay idempotent. Reach for a cloud OCR service only when you need table structure, form fields, or handwriting. And for documents your own system creates, skip recognition entirely by rendering them from HTML with a real text layer from the start.

Free tools mentioned:

Pdf To TextTry it freePdf To PngTry it freeCompress PdfTry it free

Start generating PDFs

Build PDF templates with a visual editor. Render them via API from any language in ~300ms.