Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Tesseract Ocr Laravel Package

thiagoalessio/tesseract_ocr

A simple PHP wrapper for Tesseract OCR that lets you extract text from images with a clean, fluent API. Configure languages, page segmentation, and other options easily, without dealing with shell commands directly.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a thin, dependency-free wrapper around the Tesseract binary — there is no service provider, facade, or config file shipped with it. You need both halves installed.

1. Install the system binary first (this is what people forget):

# Debian/Ubuntu
sudo apt-get install tesseract-ocr tesseract-ocr-eng tesseract-ocr-deu

# macOS
brew install tesseract tesseract-lang

# Verify
tesseract --version
tesseract --list-langs

2. Then the PHP side:

composer require thiagoalessio/tesseract_ocr

3. First use case — extract text from an uploaded invoice/receipt:

use thiagoalessio\TesseractOCR\TesseractOCR;

$text = (new TesseractOCR(storage_path('app/invoices/2024-001.png')))
    ->lang('eng')
    ->run();

// $text is a plain string of the recognized text

Where to look first:

  • The README on GitHub is genuinely the best documentation; the API is fluent and self-documenting, so vendor/thiagoalessio/tesseract_ocr/src/TesseractOCR.php is worth skimming once to see every option.
  • TesseractOCR::availableLanguages() is the fastest way to confirm your binary, path, and language packs are all wired correctly before you write any feature code.
  • Start with a single, high-quality image and ->run(). Do not start with PDFs, multi-page TIFFs, or photos from a phone — you will conclude the package is broken when the real problem is input quality.

Implementation Patterns

Wrap it in a service and bind it

Because the library is stateless and CLI-backed, the cleanest Laravel integration is a small service in the container. Put the binary path and defaults in config so staging/production can differ.

// config/ocr.php
return [
    'executable' => env('TESSERACT_PATH', '/usr/bin/tesseract'),
    'lang'       => env('TESSERACT_LANG', 'eng'),
    'psm'        => 3,
    'temp_dir'   => storage_path('app/ocr/tmp'),
];
namespace App\Services;

use thiagoalessio\TesseractOCR\TesseractOCR;

class OcrService
{
    public function make(string $absolutePath, array $opts = []): TesseractOCR
    {
        return (new TesseractOCR($absolutePath))
            ->executable(config('ocr.executable'))
            ->lang(...(array) ($opts['lang'] ?? config('ocr.lang')))
            ->psm((int) ($opts['psm'] ?? config('ocr.psm')))
            ->tempDir(config('ocr.temp_dir'));
    }

    public function text(string $absolutePath, array $opts = []): string
    {
        return trim($this->make($absolutePath, $opts)->run());
    }
}

Binding the executable() explicitly is the single highest-value thing you can do — it removes the entire class of "works on my machine" bugs when the binary lives outside $PATH (Forge, Docker, shared hosting).

Always run OCR in a queue job

OCR is CPU-bound and routinely takes 1–30 seconds per page. Doing it inline in a controller will blow up your request time and, on multi-page documents, hit PHP's max_execution_time.

class ExtractDocumentText implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

    public int $timeout = 120;   // must be < queue retry_after
    public int $tries = 3;
    public int $backoff = 30;

    public function __construct(public Document $document) {}

    public function handle(OcrService $ocr): void
    {
        $path = Storage::disk('local')->path($this->document->path);

        $text = $ocr->text($path, ['psm' => 6]);

        $this->document->update([
            'text'   => $text,
            'ocr_at' => now(),
        ]);
    }
}

Add WithoutOverlapping if a user can re-trigger extraction, and consider a dedicated ocr queue so a backlog of documents doesn't starve your mail jobs.

Preprocess before you tune Tesseract

Tesseract's accuracy is dominated by input quality. Greyscale, upscale small images to ~300 DPI equivalent, and binarize. Intervention Image v3 or Imagick both work:

$image = Image::read($uploaded)
    ->greyscale()
    ->resize(2000, null)     // upscale helps a lot for screenshots/phone photos
    ->sharpen(10);

$image->save($cleanPath);

Constrain the character set for structured fields

For license plates, MRZ lines, invoice totals, or meter readings, an allowlist is worth more than any tuning:

(new TesseractOCR($plate))
    ->allowlist('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789')
    ->psm(7)   // treat as a single text line
    ->run();

psm(7) (single line) and psm(6) (single uniform block) are the two you'll reach for most often; psm(3), the default, is a fully automatic page segmentation that can be surprisingly bad on cropped regions.

Use structured output when you need coordinates

configFile('tsv') or configFile('hocr') gives you per-word bounding boxes, confidences, and line grouping — essential if you want to highlight matched text in the UI or crop regions:

$tsv = (new TesseractOCR($path))->configFile('tsv')->run();

// tab-separated: level page_num block_num par_num line_num word_num
//               left top width height conf text
foreach (array_filter(explode("\n", $tsv)) as $row) {
    [$level, $page, $block, $par, $line, $word,
     $left, $top, $w, $h, $conf, $text] = explode("\t", $row);

    if ((int) $level === 5 && (float) $conf > 60) {
        // $text at ($left, $top) with confidence $conf
    }
}

Cache aggressively

OCR output for a given image + parameter set never changes. Hash the file and the options and cache the result:

$key = 'ocr:'.hash_file('xxh128', $path).':'.md5(json_encode($opts));

return Cache::remember($key, now()->addMonth(), fn () => $ocr->text($path, $opts));

This turns re-rendered admin pages from 8-second operations into instant ones.

Health check command

Ship an artisan command that verifies the binary and language packs, and run it in your deploy pipeline:

$this->info('Languages: '.implode(', ', TesseractOCR::availableLanguages()));

Gotchas and Tips

The binary, not the package, is the dependency. 90% of "it doesn't work" issues are TesseractNotFoundException. On Forge/Docker, the binary is often at /usr/bin/tesseract — set TESSERACT_PATH explicitly rather than trusting $PATH. On Vapor/Lambda you must bundle it in the Docker image or a layer; it is not installable at runtime.

proc_open / exec may be disabled. The wrapper shells out. Shared hosts that disable these functions in php.ini will fail with a confusing error. Check disable_functions before promising OCR to a client.

Exceptions are your control flow. run() throws UnsuccessfulCommandException, ImageNotFoundException, FileNotFoundException, and TesseractNotFoundException. Catch them in the job, mark the document as failed with a reason, and don't let one bad image retry forever — three retries against a corrupt PNG is pure waste. Conversely, Tesseract exits 0 on blank or unreadable images and returns an empty string: always check trim($text) === '' and treat that as a soft failure.

Language packs are per-language packages. ->lang('deu') fails if tesseract-ocr-deu isn't installed, even though tesseract-ocr is. Multiple languages are passed variadically (->lang('eng', 'deu')) and slow OCR roughly linearly — only enable what you need. ->lang('osd') is required for orientation detection.

Do not JSON-encode configFile('pdf') output. Any non-hocr/tsv/txt config file returns binary. Write it to disk with Storage::put() and stream it back — piping a PDF body into a JSON response produces a very confusing encoding error.

Prefer Storage::disk('local')->path() over URLs. Tesseract needs a real filesystem path. S3, GCS, and remote disks must be Storage::get() + tempnam() + cleanup first. This is the most common integration mistake in Laravel apps.

Set a temp directory you control. ->tempDir(config('ocr.temp_dir')) avoids failures in read-only or tiny /tmp mounts (common in containers). Create the directory in your deploy script. If you're hitting disk pressure, ->withoutTempFile() avoids intermediate files when capturing stdout.

Watch the concurrency. Tesseract uses OpenMP and will happily eat every core. On a queue worker running 4 processes, that's 4× N threads. ->threadLimit(2) plus a queue maxProcesses cap keeps your box from thrashing.

Queue retry_after must exceed job $timeout. A 120-second job with the default 90-second retry_after will be picked up twice and run in parallel — you'll see duplicate writes and doubled CPU load.

PDFs need rasterizing first. Hand Tesseract a PDF and you'll get partial or no results. Render pages to PNG via Imagick/Ghostscript (or use spatie/pdf-to-image) and OCR each page. For multi-page input, chain ->image() per page and concatenate output yourself.

Locale and encoding. Non-ASCII output can be mangled if the worker's locale is unset. Set LC_ALL=C.UTF-8 in the supervisor environment if you're seeing broken accented characters.

Treat config values as code, not data. Never interpolate user input into ->config() or ->configFile() — those are passed to Tesseract as arguments and can reference files on disk. Validate uploads (mime, size, dimensions) and re-encode through Intervention before OCR; that both normalizes the input and strips malicious payloads from the file.

Make it testable. run() is a hard external dependency, so define a small interface (DocumentOcr) around it and bind a fake in tests. Assertions on real OCR output are flaky across Tesseract versions — test your pre/post-processing logic, not the engine.

psm and oem are version-sensitive. Modes valid in Tesseract 4 may behave differently in 5. Pin the binary version in your Docker image so a apt-get upgrade doesn't silently change accuracy across environments.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums