cdsmths/laravel-ocr-space
Laravel OCR Space is a clean wrapper for the OCR.space API, letting you extract text from images and PDFs in Laravel. Configure your API key, send files or URLs, and get parsed text responses with minimal setup.
Install the package via Composer. Laravel 5.5+ auto-discovers the service provider and OcrSpace facade; on older versions, register Cdsmths\LaravelOcrSpace\OcrSpaceServiceProvider in config/app.php and alias the facade.
composer require cdsmths/laravel-ocr-space
php artisan vendor:publish --tag=ocr-space-config
Add your key to .env:
OCR_SPACE_API_KEY=your_real_key
The OCR.space API ships a public helloworld key for smoke-testing — the package's config will default to it in some versions, so always set a real key before you ship anything; the shared key is heavily rate-limited and sometimes returns E101 errors under load.
Look first at: (1) config/ocr-space.php for endpoint, default language, and engine selection; (2) the facade class under src/ for the exact method names available — the API surface is small (parse-by-file, parse-by-URL, parse-base64) and reading the class is faster than guessing.
First use case — OCR a local image in a controller or artisan command:
use Cdsmths\LaravelOcrSpace\Facades\OcrSpace;
$result = OcrSpace::parseFile(storage_path('app/receipt.png'));
$text = $result->parsedText(); // or ->ParsedResults[0]->ParsedText depending on version
Run it once inside php artisan tinker with a small JPEG before wiring it into anything else — you'll confirm the key, network reachability, and the shape of the response object in one shot.
Wrap calls in a dedicated service. Don't call the facade from controllers. Bind a small ReceiptOcrService that depends on OcrSpace, so you can swap engines, add retries, and test with a fake.
Parse by URL for remote files. If your uploads live on S3, generate a temporary signed URL and pass it to the URL-based method instead of downloading to /tmp and uploading again. This halves the latency and skips the free-tier 1 MB upload path entirely — OCR.space fetches the URL server-side.
Store the raw response, not just the text. OCR.space returns ParsedResults[] with ParsedText, TextOverlay.Lines[] (word bounding boxes when isOverlayRequired=true), ErrorMessage, and a top-level IsErroredOnProcessing flag. Persisting the JSON lets you backfill searchable PDFs, re-run layout analysis, or debug a bad parse months later. A json column plus a TEXT column for ParsedText is usually enough.
Pick OCREngine deliberately. Engine 1 is the legacy/default and is faster; engine 2 handles handwriting and dense receipts noticeably better but is ~2–3× slower and more restrictive on free quota. Make it a config value (e.g. OCR_ENGINE=2), not a hardcoded argument.
Use queue jobs for anything user-facing. A 5-page PDF at engine 2 routinely takes 20–40 seconds. Dispatch a ProcessOcrDocument job, broadcast a DocumentParsed event on success, and let the UI poll or subscribe. Set $timeout = 120 and $tries = 3 with a backoff — transient E500 / E101 responses from OCR.space are common enough that a single attempt will bite you.
Batch with Concurrency or chunked jobs. If you're ingesting a folder of scans, wrap in Bus::batch([...])->dispatch() rather than foreach — each OCR call is I/O-bound and Laravel's concurrency helper (Illuminate\Support\Facades\Concurrency) will finish a 20-image batch in a fraction of the wall time.
Language codes are ISO-639-1 or OCR.space's extended set. eng, deu, fra, spa, chi_sim, etc. Pass as an option per call and let it default from config; users frequently need spa + eng for mixed invoices (comma-separated works).
Saving searchable PDFs. With iscreatesearchablepdf=true and isOverlayRequired=true, the API returns a base64 PDF in the response. Decode it, put it on the private disk, and serve via a signed route — don't return the base64 blob in an API response.
Testing. Use Http::fake([...]) with a canned OCR.space payload. The package's parse methods ultimately hit api.ocr.space via Http::post, so the standard fake covers them. Assert on your own DTO, not on the raw array, or you'll be re-recorded fixtures every time the package bumps.
ParsedResults can be present but empty. Don't check $result->ParsedResults[0]->ParsedText without first confirming !$result->IsErroredOnProcessing and that count($result->ParsedResults) > 0. A blank image returns a successful response with an empty string, not an error — plan for "OCR succeeded but found nothing."E202/E300 back after a full round-trip. Add a Validator or rules() that checks $file->getSize() and page count before dispatching.filetype must match the bytes. If you're uploading a PNG but declare filetype=jpg, OCR.space may still parse it, but the error messages when it doesn't are useless. Derive filetype from $file->getMimeType() or Str::afterLast($path, '.').config:cache in a deploy script but the key is read via env() inside the package class (rather than config()), the cached config will silently use the default helloworld key. Verify by logging the key prefix on boot in staging.Http client defaults to 30s. Engine 2 on a PDF frequently exceeds this. Publish the config and bump HTTP_TIMEOUT to 90+ for queues; leave it at 30 for interactive calls.scale=true improves accuracy on small text but inflates the response. Bounding boxes come back in the scaled coordinate space — divide by the scale factor before drawing overlays on your UI, or your highlights will be offset.E101 (invalid key — usually transient overload on the shared key), E500, E501, timeouts. Do not retry on E200 (invalid params) or E300 (invalid file) — you'll just burn quota.isOverlayRequired=true and inspect the overlay — if TextOverlay.Lines is empty too, the image is genuinely blank or upside-down; add detectOrientation=true and re-run before assuming a code bug."cdsmths/laravel-ocr-space": "^1.0" rather than "*" — and read the diff before bumping, since facade method signatures are not covered by Laravel's deprecation cycle.How can I help you explore Laravel packages today?