Tools,

Paperless-ngx OCR, tested on scanned PDFs

13

words kept from 11 pages, OCR skipped

Get the fix

A plain scan works

1,874 of 1,955 true words found

41 s for 10 pages, on 2 CPUs

I saved this as upload.sh, then sent the 10-page scan:

curl -s -H "Authorization: Token $PAPERLESS_TOKEN" \
  -F "document=@$1" "$PAPERLESS/api/documents/post_document/"

Then I read the stored text, with the document ID:

curl -s -H "Authorization: Token $PAPERLESS_TOKEN" \
  "$PAPERLESS/api/documents/$1/" | jq -r .content

Paperless ran OCRmyPDF on every page, and a search for three words of the scan found it.

One typed page turns OCR off

Then I put a typed cover page in front of the same scan. Paperless was done in 0.6 seconds and stored only the 13 cover words.

Paperless 3.x checks a PDF for text first. More than 50 characters counts as a digital PDF: no archive file, and no OCR on any page. Search found nothing.

Fix: always make the archive file

Add this line to docker-compose.env and restart:

PAPERLESS_ARCHIVE_FILE_GENERATION=always

Documents you added before keep their old text. Reprocess each one by its ID:

curl -s -X POST -H "Authorization: Token $PAPERLESS_TOKEN" \
  -H "Content-Type: application/json" -d "{\"documents\": [$1]}" \
  "$PAPERLESS/api/documents/reprocess/"

1,876 of 1,955 scan words found

13 of 13 cover words kept

OCRmyPDF kept the typed page as it was and read the scanned ones, in 40 seconds.

Set the language, or lose umlauts

The default OCR language is English. I sent 3 proofread German pages, then sent them again with this line:

PAPERLESS_OCR_LANGUAGE=deu+eng
3 German pages, 1,887 words, 206 umlauts
Setup Words right Umlauts right
eng, the default 1,624 0
deu+eng 1,843 183
Our OCR Actor, deu 1,853 192

Umlauts here are ä, ö, ü and ß. The Actor row is explained below.

Other languages need an install line

The image has 5 languages: deu, eng, fra, ita and spa. With PAPERLESS_OCR_LANGUAGE=por alone, the container restarted 11 times. The log said:

?: The selected ocr language por is not installed. Paperless cannot OCR your documents without it. Please fix PAPERLESS_OCR_LANGUAGE.

Name the language pack in a second line too:

PAPERLESS_OCR_LANGUAGE=por
PAPERLESS_OCR_LANGUAGES=por

The container installed tesseract-ocr-por at start and was up in 13 seconds.

The sample env file stops 3.4.2

The sample docker-compose.env sets PAPERLESS_SECRET_KEY=change-me, and 3.4.2 refuses that value. The container restarted 7 times in 43 seconds. I ran this in the compose folder:

echo "PAPERLESS_SECRET_KEY=$(python3 -c 'import secrets; print(secrets.token_urlsafe(64))')" >> docker-compose.env

It adds a random key on a new last line, and the last line wins. The container then started in 13 seconds.

Want Markdown out? Use the Actor

Paperless OCR is free and good. Our OCR Actor gives Markdown, in 10 languages, with no server. On the German pages:

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/paperless-german-3-pages.pdf"], "language": "deu", "markdownTables": false}' \
  | jq -r '.[].markdown' > german.md
sh upload.sh german.md

899 of 915 words on the one-column pages, Paperless 896

954 of 972 on the two-column page, Paperless 947

Build 0.1.65, live since Nov 11, reads the two columns in order. Build 0.1.64 mixed them and got 495. Paperless took its Markdown as a text document. It costs $0.006 a page.

How I tested it
  • Scan: the 10-page PDF of OCR in n8n, proofread Wikisource book scans with no text layer. 1,955 true words.
  • Typed page: a 13-word cover sheet in front of the scan, the file of AnythingLLM OCR.
  • German: 3 proofread de.wikisource pages from 3 books, from the Tesseract German test.
  • Score: true words found in order, with the scorer of the tessdata test. Cover-first file: 2, cover words that the scan also has.
  • Paperless-ngx 3.4.2 on arm64, with Tesseract 5.5.0 and OCRmyPDF 17.12.1. The repo's docker-compose.sqlite.yml, 2 CPUs.
  • A new instance for each setup, because Paperless refuses a duplicate file. The search was Hatton yacht lifeboat.
  • The code blocks above are the files that ran. The Actor: two real runs on build 0.1.64, tables on and off, read back from Paperless. Then two real runs on build 0.1.65, tables off and on, with the same words right. The umlauts are from the tables-on run.
What I did not test
  • Tika and Gotenberg for office files, and remote OCR.
  • The consume folder, email import, and Paperless-ngx 2.x.
  • Handwriting, tables, and languages other than German.
Sources

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

Why does Paperless-ngx not OCR my PDF?

In 3.x, a PDF with more than 50 characters of text counts as digital, so Paperless skips OCR on every page. Set PAPERLESS_ARCHIVE_FILE_GENERATION=always and reprocess the document.

How do I set more than one OCR language in Paperless-ngx?

Join them with a plus: PAPERLESS_OCR_LANGUAGE=deu+eng. For a language outside deu, eng, fra, ita and spa, also name it in PAPERLESS_OCR_LANGUAGES, or the container does not start.

What happened to PAPERLESS_OCR_MODE=skip in 3.x?

Paperless-ngx 3.4.2 treats skip and skip_noarchive as auto, with warning W003. The modes are now auto, force, redo and off. Archive files have their own setting, PAPERLESS_ARCHIVE_FILE_GENERATION.