Tools,

pytesseract config: the mistakes it hides

6 of 12

mistakes gave no Python error

Try it on your PDF, $0.006 a page

pytesseract hides Tesseract's warnings

Letters left after a digits-only whitelist, 6 pages
No config
2,712
tessedit_char_whitelist
0
tessedit_char_whitelst
2,712
TESSEDIT_CHAR_WHITELIST
2,712

These ran on 6 test pages with no Python error. The first 4 gave the same text as no config:

config= What Tesseract printed
-c tessedit_char_whitelst=0123456789 Warning: The parameter 'tessedit_char_whitelst' was not found.
-c TESSEDIT_CHAR_WHITELIST=0123456789 The same warning. Names are case sensitive.
digts read_params_file: Can't open digts
psm 6 Can't open psm, then Can't open 6: two file names
digits --psm 6 Can't open --psm. Options after a file name are file names too. Same text as digits alone.
-c tessedit_char_whitelist=0-9 Nothing. That is a list of 3 characters, not a range. 2 of 726 words came out right.

The loud mistakes, 2 with an empty message

These raised TesseractError on all 6 pages:

config= The error
-psm 6 (1, "Error, unknown command line argument '-psm'")
--psm=6 The same, for '--psm=6'
-c tessedit_char_whitelist = 0123456789 (1, "exception: Missing '=' in configvar assignment")
--oem 0 Error: Tesseract (legacy) engine requested, but components are not present
--psm 14 (1, '')
--psm 6, --oem 1 (1, '')

The empty ones are a bad psm value. Tesseract prints Invalid PSM value to standard output, not to the error stream, so pytesseract has no message to show.

--oem 0 asks for the legacy engine, which the tessdata_fast model leaves out. See tessdata_best vs tessdata_fast.

Check a config once: 11 of 12 caught

It reruns the config on the command line and fails on hidden warnings:

import shlex
import subprocess

import pytesseract


def check_config(config, image):
    """Fail on the warnings pytesseract hides."""
    cmd = pytesseract.pytesseract.tesseract_cmd
    args = [cmd, image, "stdout", *shlex.split(config)]
    r = subprocess.run(args, capture_output=True, text=True)
    lines = r.stderr.splitlines()
    bad = [x for x in lines if "not found" in x or "Can't open" in x]
    if r.returncode or bad:
        raise ValueError(bad or r.stderr.strip() or r.stdout.strip())
    return config


config = "-c tessedit_char_whitelst=0123456789"
check_config(config, "invoice.png")

It stopped with:

ValueError: ["Warning: The parameter 'tessedit_char_whitelst' was not found."]

On all 6 pages, it stopped 11 of the 12 mistakes and passed every good config. It misses the range, which is a valid list to Tesseract.

What worked, and what each did

config= On my 6 pages
--psm 6 724 of 726 words right, against 693 with no config
--psm 6 --oem 1 The same text as --psm 6
-c tessedit_char_whitelist=0123456789 "Invoice 2026-0417" came out as 20260417
digits 2026-0417: the file also allows the dash and the point
-c preserve_interword_spaces=1 Kept the gaps between columns, as below
import pytesseract

config = "--psm 6 -c preserve_interword_spaces=1"
text = pytesseract.image_to_string(
    "invoice.png", lang="eng", config=config
)
print(text.splitlines()[4])
Date: October 30, 2026      Due: November 29, 2026

Pass the language as lang. pytesseract puts -l and the language before your config.

631 parameters: search for the name

tesseract --print-parameters lists every name Tesseract 5.5.3 knows. Search it before you type one:

$ tesseract --print-parameters | grep whitelist
tessedit_char_whitelist		Whitelist of chars to recognize

A bare word in config is a file name. The Homebrew tessdata/configs folder holds 26, such as digits, hocr and tsv. The digits file is one line: tessedit_char_whitelist 0123456789-.

If you want the text, not the tuning

PDF OCR to Markdown is our Apify Actor. On scanned pages it runs Tesseract with fixed settings, so you pass no config. You get Markdown, with tables as Markdown tables. I ran check_config on 4 of its config strings, and all 4 passed.

Questions people ask

What config options does pytesseract accept?

The options of the tesseract command: --psm, --oem, --dpi, --tessdata-dir, --user-words, --user-patterns, --loglevel and -c name=value, then config file names. pytesseract splits the string like a shell and puts it after -l and the language, if you pass lang.

Why is my tessedit_char_whitelist not working?

Check the spelling and the capitals first: a wrong name only prints a warning, and pytesseract hides it. Then check the value. 0-9 is not a range. A space before the equals sign stops the run, and a space after it drops the whitelist with no error.

How do I pass psm and oem in pytesseract?

Use two dashes and a space, as in config="--psm 6 --oem 1". -psm 6 and --psm=6 raised TesseractError. psm 6 with no dashes ran, but Tesseract took psm and 6 as two file names it could not open.

How do I see Tesseract warnings from pytesseract?

pytesseract shows the error stream only when Tesseract fails. Otherwise it drops it. Run the same arguments with subprocess and read stderr yourself, as check_config on this page does.

What is a Tesseract config file?

A text file with one name and value per line, in tessdata/configs. The digits file holds tessedit_char_whitelist 0123456789-. Put its name at the end of config, after every option.

How many Tesseract parameters are there?

tesseract --print-parameters listed 631 in Tesseract 5.5.3. The names are case sensitive.

Method, limits and sources

How I tested it

Tesseract 5.5.3 from Homebrew, pytesseract 0.3.13 and Python 3.14.7 on a Mac, one thread, with the eng model from tessdata_fast 4.1.0. The 6 pages are the ones from Tesseract bounding boxes: a notice, an invoice and minutes, clean and rough, 726 words.

Each of 18 configs went through image_to_string on every page, and through the command line to read the error stream. The scripts and output are in research/bench/tesseract-config-2026-11-01/.

What I did not test
  • Windows. pytesseract splits config with posix=False there, so quote marks act differently.
  • Tesseract 4, tesserocr, tesseract.js and other languages.
  • Real scans. The pages are ones we wrote and made to look like scans.
Sources