Paperless-ngx OCR, tested on scanned PDFs
13
words kept from 11 pages, OCR skipped
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
| 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.
Or send us the scan. Tack, our AI, does the job for $29, up to 50 pages. First 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.
Related guides
- Docspell OCR, tested on scanned PDFs
- Papermerge OCR, tested on scanned PDFs
- OCRmyPDF "page already has text": which flag to use
- OCRmyPDF language: set -l, or lose the accents
- Tesseract and German: eng reads no ä, ö, ü or ß
- Dify OCR, tested on a scanned PDF
- Stirling-PDF OCR, tested on scanned PDFs
- Nextcloud OCR, tested on scanned PDFs
- Immich OCR, tested on photos of pages
- How to make a PDF searchable, in one command
- A PDF OCR API in one curl call