imrandevbd / laravel-ai-ocr
Private manual-first document extraction with local OCR, quality scoring and targeted AI Hub fallback for Laravel.
Requires
- php: ^8.2
- ext-fileinfo: *
- ext-mbstring: *
- illuminate/support: ^10|^11|^12|^13
- illuminate/validation: ^10|^11|^12|^13
- imrandevbd/laravel-ai-hub: ^1.6
- psr/http-message: ^1|^2
- symfony/process: ^6.2|^7|^8
Requires (Dev)
- larastan/larastan: ^2.9|^3
- laravel/pint: ^1
- orchestra/testbench: ^8|^9|^10|^11
- phpstan/phpstan: ^1.12|^2
- phpunit/phpunit: ^10|^11|^12|^13
Suggests
- ext-gd: Image resizing and preprocessing
- ext-imagick: Image preprocessing, HEIC and TIFF rasterization
- filament/filament: Optional Filament integration
- phpoffice/phpspreadsheet: Legacy XLS support
- phpoffice/phpword: Additional Word formats
- smalot/pdfparser: Alternative PDF text extraction
Provides
None
Conflicts
None
Replaces
None
README
Laravel AI OCR
Turn documents into structured, validated data your Laravel application can use.
Manual first. AI when needed. Visibility at every step.
Quick start · Screenshots · Business opportunities · Time savings · Documentation
A supplier sends a PDF. A customer uploads a photograph. Your team receives a folder of receipts, a scanned form, or an email with attachments. Someone still has to open each file, copy its contents, check the numbers, and put the data into your system.
Laravel AI OCR gives your application a reusable document intake pipeline. Extract readable text locally, run Tesseract on weak pages, and use AI only where the quality checks say it is needed. Get text, structured fields, validation, confidence, evidence, warnings, and usage through one fluent API.
use ImranDevBd\AiOcr\Facades\AiOcr; $result = AiOcr::file($request->file('invoice')) ->as('invoice') ->languages(['ben', 'eng']) ->maxCost(0.05) ->extract(); $invoice = $result->data(); $totalMinorUnits = $result->field('total')->value;
Current release: v1.1.0. Visual AI provider configuration is available directly inside OCR Studio. Composer versions come from Git tags; Packagist must index the tag before it appears there as a stable version. Local checks include fresh Laravel 10–13 installations, 11 PHP/Laravel runtime combinations, Filament 3/4, level-8 static analysis, and the coverage gates described in the validation report. Laravel 13 requires PHP 8.3+. Historical framework compatibility does not imply those dependencies remain secure for production.
Explore this README
- Why teams use it
- Business opportunities and time savings
- Studio screenshots
- Complete capability map
- Formats and inputs
- Installation and quick start
- Fallback decisions and strategies
- 23 document types and custom schemas
- Results and exports
- Queues, batches and streaming
- Persistence, history and reviews
- Studio, Filament and HTTP API
- Cost, privacy and security
- Configuration, CLI, testing
- Extending the package, FAQ, documentation
Why teams use it
| Your operational problem | What the package provides | The opportunity |
|---|---|---|
| Staff repeatedly retype document fields | Native extraction, local OCR and schema mapping | Spend less time transferring data between systems |
| Every file is sent to a paid model | Local-first processing and targeted fallback | Reserve paid AI calls for difficult pages or fields |
| Different suppliers use different layouts | Built-in types, custom schemas and AI-assisted repair | Build a consistent intake interface around varied documents |
| Important fields disappear inside long text | Typed fields, nested data, tables and exports | Feed business records, search, reports and review screens |
| Operations cannot explain an extracted number | Field confidence, page references, source quotes and trace | Give reviewers context before they accept a value |
| Large uploads hold up web requests | Encrypted jobs, batches, progress and page merging | Move document work into background workflows |
| Sensitive documents need tighter boundaries | Manual-only mode, local-only policy, redaction and encryption | Choose how each class of document can be processed |
| OCR behavior is hard to troubleshoot | Studio, Doctor, warnings, usage and tests | Diagnose missing tools and weak results with visible evidence |
The package handles document extraction. Your Laravel application supplies the business workflow: approvals, ERP posting, customer onboarding, notifications, billing, and application-specific access rules.
What can your business build?
| Business or team | Example workflow | What you can automate or assist |
|---|---|---|
| Accounting and finance | Invoice or receipt → fields → review → accounting record | Invoice numbers, dates, totals, currencies, line-item candidates and arithmetic checks |
| Retail and e-commerce | Purchase order or supplier invoice → import queue | Reduce repetitive entry into purchasing and stock-management workflows |
| Logistics and distribution | Shipping label, packing list or bill of lading → shipment draft | Extract references, addresses, tracking numbers and item candidates |
| Banks, fintech and onboarding teams | Identity document → restricted extraction → staff review | Bangla/English fields, number-format checks and passport MRZ validation |
| SMEs and agencies | Uploaded receipts and business cards → organized records | Expense intake, contact drafts and searchable document text |
| HR and recruitment | CV or résumé → structured profile draft | Names, contact details, experience and education candidates |
| Clinics and health software teams | Report or prescription → review queue | Prepare structured drafts from patient/report fields; professional review remains part of the host workflow |
| Legal and administration | Contract or form → searchable fields | Capture parties, dates and basic metadata for review and indexing |
| SaaS builders | Customer upload → owned extraction → API response | Add document intake features to an existing Laravel product |
| Internal operations | Legacy files or MIME email attachments → batch import | Consolidate document handling behind one extraction interface |
These are integration opportunities, not bundled accounting, KYC, medical, hiring or legal decision systems. Document type schemas help organize data; they do not establish the truth of a document or replace your review rules.
A practical route from prototype to production
- Start with one document family and a small, representative sample set.
- Run
manualOnly()to establish the local extraction baseline. - Define the fields and checks that make a result usable in your business.
- Send uncertain records to a review queue; enable narrowly scoped AI fallback where permitted.
- Measure handling time, correction rate and actual cost before expanding to more suppliers or document types.
How much time could you save?
The most useful saving is staff handling time, not a promise that every page finishes in a fixed number of milliseconds. Replace repeated copying with a workflow where staff review extracted values and resolve exceptions.
The example below assumes 3 minutes of manual handling becomes 45 seconds of review per document. That saves 2 minutes 15 seconds, or 75% of handling time under those assumptions.
| Documents per month | Manual handling at 3 min each | Review at 45 sec each | Illustrative hours released |
|---|---|---|---|
| 500 | 25 hours | 6.25 hours | 18.75 hours / month |
| 2,000 | 100 hours | 25 hours | 75 hours / month |
| 10,000 | 500 hours | 125 hours | 375 hours / month |
Illustrative calculation, not a measured customer result or guaranteed saving. Your review time depends on document quality, field count, layouts, languages, exception rate and downstream work. Setup, integration, infrastructure, AI charges and ongoing maintenance are additional costs. Staff time released is capacity; it becomes a cash saving only when your business can realize it.
Monthly staff hours released
= documents × (manual seconds − assisted review seconds) ÷ 3,600
Illustrative monthly labor capacity value
= hours released × your fully loaded hourly staff cost
Estimated net benefit
= realized labor value − processing costs − allocated implementation/maintenance costs
For the 2,000-document example, 75 hours × $12/hour = $900/month of labor capacity value, before costs. Substitute your own currency, wage and measured handling times. Use ai-ocr:benchmark for pipeline throughput, result usage for AI spend, and a timed business pilot for end-to-end handling savings. OCR confidence alone is not an accuracy measurement.
See the Studio
These are screenshots of the actual bundled application, captured locally with a fictional synthetic invoice. The extraction shown used local Tesseract with $0 AI spend. The sample's confidence and duration are specific to that run and are not a performance guarantee.
Upload a document. Inspect the result. Follow the trace.
Preview the source beside the extracted text and structured JSON. See the engine, word confidence, warnings, cost, and processing steps. Download the result without leaving Studio.
Define the fields your business needs
Build a schema, apply it to the Playground, and save reusable templates when persistence and an authenticated owner are configured. Nested schemas and advanced rules are also available through the PHP API.
View the Doctor screen: binaries, languages, optional dependencies and limits
Doctor reports the capabilities of the current installation, including missing optional components. It helps you decide what to install for your workload.
Screenshots are stored in this repository, so GitHub can display them without a running demo server. See screenshot notes for their provenance.
Complete capability map
| Area | Included capabilities |
|---|---|
| Input handling | Uploads, local paths, Laravel storage disks, raw bytes, base64/data URIs, readable PHP streams, PSR streams, public URLs and the media-source contract |
| Native extraction | Poppler PDF text, optional Smalot fallback, conservative minimal PDF fallback, DOCX, XLSX, optional XLS, CSV/TSV, HTML, plain text and recursive MIME email attachments |
| PDF metadata | Page information, best-effort literal AcroForm values and opt-in bounded embedded-image extraction |
| Rasterization | Poppler/Imagick page rendering with configurable DPI and pixel limits; optional multipage TIFF/HEIC support through Imagick |
| Local OCR | Tesseract text, TSV word boxes and confidence, selected language packs, orientation/script information and bounded concurrent processes |
| Preprocessing | Configurable deskew, grayscale and denoise where the installed image backend supports them |
| Quality | Minimum text length, page score, OCR confidence, garbled-text heuristics and expected-script checks |
| AI recovery | Failed-page vision, restricted text-only missing-field repair, semantic retry and fast/strong model escalation |
| Strategies | Auto, manual-only, AI-only, AI verification and weighted field consensus; optional additional consensus models |
| Structured outputs | Fluent or bounded raw JSON schemas, nested objects/arrays, required/nullable fields, enums, regex, custom casts/validators and Laravel rules |
| Business validation | Document-specific cross-checks, money in integer minor units, Bangla digits/dates, BD phone normalization, Luhn/IBAN/MRZ helpers |
| Evidence | Field value, confidence, source page, engine, validation status and source quote; ungrounded AI field evidence is rejected |
| Results | Text, page/word data, tables, structured values, metadata, languages, engine summary, warnings, trace and measured usage |
| Exports | JSON, CSV, Markdown, HTML, table exports and saving to a path or application model |
| Cost management | Page/cost limits, model-price preflight, AI Hub budgets, usage tags, encrypted cache and owner-scoped deduplication |
| Privacy | Manual-only mode, provider allowlists, document-specific local-only policy, reversible text redaction and content-log scrubbing |
| Persistence | Optional ULID document/extraction/page/schema records, encrypted result/text, no-text mode, optional encrypted original retention and pruning |
| Background work | Encrypted jobs, document batches, page batches, ordered merging, callbacks, progress tracking and extraction events |
| Integrations | Laravel facade/helper, Eloquent ownership trait, opt-in HTTP API, signed metadata webhooks and replaceable driver contracts |
| User interface | Blade Studio, upload/drop/preview, strategy controls, compare view, trace, JSON download, schema templates, history/review/rerun and analytics |
| Administration | Eight Artisan commands, Doctor diagnostics and optional Filament 3/4 resource, upload link action and statistics widget |
| Developer experience | Fakes/assertions, synthetic fixtures, offline tests, PHPStan level 8, Pint, coverage gates, Docker and compatibility CI configuration |
Capabilities depend on installed binaries/codecs and the document itself. Native table/field extraction is best-effort; arbitrary complex layouts may require schema-guided AI and review. See implementation boundaries.
Supported formats and inputs
File formats
| Document | Processing path | What to install |
|---|---|---|
| Text-based PDF | Native text first; page quality checks; local OCR/AI recovery if needed | Poppler recommended; Smalot optional |
| Scanned or mixed PDF | Page rasterization → local OCR → targeted AI fallback | Poppler or Imagick, plus Tesseract for local OCR |
| JPEG, PNG, WebP, GIF and supported raster images | Local OCR; optional preprocessing and AI vision | Tesseract; Imagick/GD for image processing |
| HEIC and multipage TIFF | Decode/rasterize pages before extraction | Imagick with the required format codecs |
| DOCX | ZIP/XML extraction | PHP ZIP and DOM extensions |
| XLSX | ZIP/XML cells and table data | PHP ZIP and DOM extensions |
| Legacy XLS | Spreadsheet reader | Optional phpoffice/phpspreadsheet |
| CSV / TSV | Delimited text and tables | Core PHP capabilities |
| HTML / TXT | Text parsing and cleanup | DOM for HTML |
| EML | MIME body and nested attachments within configured bounds | Format-specific dependencies for each attachment |
MIME detection uses file contents rather than trusting an upload extension. SVG and unsupported formats are rejected. HEIC/TIFF support depends on the actual Imagick build and its security policy.
Bring documents from the systems you already use
// Laravel upload or a durable local path. AiOcr::file($request->file('document'))->extract(); AiOcr::file(storage_path('app/private/invoice.pdf'))->extract(); // A configured Laravel disk, including a remote disk with readable streams. AiOcr::file('invoices/2026-1042.pdf')->disk('s3')->extract(); // Raw bytes, a base64 document, a data URI or a readable stream. AiOcr::file($bytes)->extract(); AiOcr::file($dataUri)->extract(); AiOcr::file($stream)->extract(); // Public HTTP(S) document URL; SSRF protections apply. AiOcr::url($publicDocumentUrl)->extract(); // The helper begins a single-file extraction request. $result = ai_ocr($file)->manualOnly()->extract();
PSR streams are detached/consumed by the resolver. Files supplied by application paths are trusted application inputs: never pass an unvalidated request path directly to the filesystem API.
Installation
Requirements
- PHP 8.2+, Laravel 10–13; Laravel 13 requires PHP 8.3+.
- PHP
fileinfoandmbstring; additional extensions depend on the formats you process. - Hard AI integration dependency:
imrandevbd/laravel-ai-hub^1.6, installed by Composer. An AI API key is optional for manual extraction. - Poppler and Tesseract for the recommended PDF/local OCR workflow.
- Laravel
APP_KEYfor encrypted caches, optional persistence and encrypted queued jobs.
Install from source
For local development, clone/download this repository and use a Composer path repository in your Laravel application. The @dev constraint below is only for a local working checkout:
composer config repositories.ai-ocr path /absolute/path/to/laravel-ai-ocr composer require imrandevbd/laravel-ai-ocr:@dev php artisan ai-ocr:install
Once Packagist has indexed the release, install the stable version with:
composer require imrandevbd/laravel-ai-ocr:^1.0 php artisan ai-ocr:install
Laravel package discovery registers the service provider and facade. The install command publishes configuration/migrations and runs diagnostics; run migrations when you enable persistence.
Install local document tools
Ubuntu/Debian example:
sudo apt-get update sudo apt-get install poppler-utils tesseract-ocr tesseract-ocr-eng tesseract-ocr-ben php artisan ai-ocr:doctor
The binary installation guide covers macOS, Windows, Docker, FastPanel/VPS notes, binary paths and language packs. No Node build is required for Studio.
Choose your starting policy
AI_OCR_STRATEGY=auto AI_OCR_AI=true AI_OCR_STORE=false AI_OCR_STUDIO=false
Configure model providers and their credentials visually in OCR Studio → AI Providers, or in AI Hub. Both interfaces use AI Hub’s existing settings store; OCR does not maintain a second key store. Keep Studio off until its access policy is configured. Use AI_OCR_AI=false for a local-only deployment.
Add AI API keys visually
AI_OCR_STUDIO=true AI_HUB_UI=true
After authorizing both interfaces, open
/ai-ocr → AI Providers → paste API key → choose model → Save.
You can enable/disable a provider, make it the default, and select it for the
Playground. No .env key editing is required when using the Hub settings backend.
Blank keys keep existing credentials; saving sends no AI inference request.
Full visual setup guide, permissions and environment alternatives.
Before your first production deployment
- Run
ai-ocr:doctoron the same environment used by queue workers. - Configure Studio/API authorization and document-specific privacy policies.
- Verify required language packs, private shared storage,
APP_KEYand queue workers. - Test representative documents, set AI spending limits, and define a human-review threshold.
- Schedule retention pruning and monitor warnings, corrections and actual provider usage.
Quick start
1. Extract readable text without AI
$text = AiOcr::file($request->file('document')) ->manualOnly() ->text();
No AI account or model key is needed. Available native parsers and Tesseract do the work.
2. Create an invoice draft with field validation
$result = AiOcr::file($file) ->as('invoice') ->validate(['total' => 'required|integer|min:0']) ->maxCost(0.05) ->extract(); $total = $result->field('total'); if ($result->failed || ! $total->validated || $total->confidence < 0.8) { // Send this result to your application's review workflow. } // For the USD 264.00 screenshot sample, total is integer 26400. $totalMinorUnits = $total->value;
A validated value passes the configured checks; it is not independent proof that a document is authentic or that every business field is correct. Review the complete result before automatic posting.
3. Process Bangla and English identity documents locally
$result = AiOcr::file($nid) ->as('bd_nid') ->languages(['ben', 'eng']) ->manualOnly() ->extract();
Install ben.traineddata. The built-in NID policy also defaults to local-only when AI is enabled; approved local Ollama inference may be available under that policy. Number shape validation is not identity verification.
4. Limit work to selected pages
$result = AiOcr::file($largePdf) ->pages('1-3,7') ->dpi(300) ->minConfidence(0.8) ->maxCost(0.10) ->extract();
5. Receive a failure result instead of an extraction exception
$result = AiOcr::file($file)->safe()->extract(); if ($result->failed) { // Inspect warnings and route the document to an operator. }
Missing AI credentials alone do not make ordinary manual extraction fail. Without an available recovery path, the package returns its best result with honest warnings. Use requireAi() when your workflow explicitly requires AI availability.
How manual-first extraction works
flowchart TD
A[Upload, file, stream, disk or URL] --> B[Validate MIME, size, archive and network limits]
B --> C[Fingerprint and encrypted cache lookup]
C --> D[Native extraction]
D --> E{Page quality acceptable?}
E -->|Yes| H[Keep local candidate]
E -->|No| F[Rasterize and run local OCR]
F --> G{Quality acceptable now?}
G -->|Yes| H
G -->|No| I{AI permitted, ready and within limits?}
I -->|Yes| J[AI on failed pages]
I -->|No| K[Best local result plus warnings]
J --> L[Normalize and map schema]
H --> L
K --> L
L --> M{Fields missing or invalid?}
M -->|Yes, AI permitted| N[Restricted text-only field repair]
N --> O[Validate types, rules, evidence and cross-checks]
M -->|No, or no AI available| O
O --> P[Result, trace, usage and optional persistence]
Loading
The quality gate considers text length, confidence, garbled sequences and expected script. A legacy Bangla text layer can therefore trigger recovery even when the PDF technically contains selectable text.
For structured recovery, the package restricts the requested schema and checks returned evidence against source pages. AI requests have no tools. A semantic failure can trigger retry or a configured stronger model. Quality details and AI Hub source findings explain the boundaries.
Choose your extraction strategy
| Strategy | Behavior | Useful starting point |
|---|---|---|
auto |
Native → local OCR → quality-driven page/field AI fallback | General document intake with controlled fallback |
manual_only |
Native and local OCR; no AI requests | Offline processing, sensitive documents, baseline measurement |
ai_only |
AI transcription for images/PDFs; parsed text formats supplied as AI input | Testing model-based extraction or AI-specific workflows |
ai_verify |
Compare against AI while retaining manual page text and recording differences | Review workflows where a second candidate helps |
consensus |
Compare page candidates and use weighted, grounded field agreement | Documents that benefit from multiple extraction candidates |
AiOcr::file($file)->using('ai_verify')->as('invoice')->extract(); AiOcr::file($file)->using('consensus')->as('invoice')->extract(); AiOcr::file($file)->aiOnly()->provider('openai')->model($model)->extract();
withoutAi() aliases manualOnly(). Configure ai.consensus_models for additional model votes. Verification and consensus may increase spend and latency; agreement is supporting evidence, not a guarantee of correctness.
23 built-in document types
| Family | Registered types | Fields and workflow opportunities |
|---|---|---|
| Purchasing and expenses | invoice, receipt, purchase_order, utility_bill |
Number, date, subtotal, tax, discount, total, currency, item candidates, seller/buyer |
| Banking | bank_statement |
Account, bank, period, balances and transaction candidates |
| Payments | cheque |
Cheque number, bank, payee, amount and date |
| Shipping | bill_of_lading, packing_list, shipping_label |
Reference, sender/recipient, address, tracking and items |
| Identity | bd_nid, passport, id_card, driving_license, bd_trade_license |
Type-specific names, numbers and dates; NID shape and passport MRZ checks |
| Contacts and people | business_card, resume, cv |
Contact/profile fields, experience and education candidates |
| Legal and health | contract_basic, lab_report, prescription |
Basic parties/dates, test values or medication candidates for review |
| General purpose | table_only, generic_key_value, handwritten_note |
Rows, key/value entries, or note text and language |
$result = AiOcr::file($file)->as('receipt')->extract(); $result = AiOcr::file($file)->autoDetectType()->extract();
Auto-detection uses deterministic text heuristics, then policy-controlled text-only AI for ambiguous documents when available. Unknown files can remain unclassified. Types define extraction targets and checks; difficult line items or layouts may require AI or custom mapping.
Your document, your schema
Build the structure your application actually needs:
use ImranDevBd\AiOcr\Schema\Schema; $schema = Schema::make('supplier_invoice') ->string('invoice_number')->required()->description('Printed invoice ID') ->date('issue_date')->nullable() ->money('total')->required() ->string('currency')->enum(['BDT', 'USD']) ->object('supplier', fn ($s) => $s->string('name')->required()) ->array('items', fn ($i) => $i ->string('description') ->number('quantity') ->money('amount')); $result = AiOcr::file($file) ->as($schema) ->validate(['total' => 'integer|min:0']) ->extract();
Available features include nested objects/arrays, dotted field names, required/nullable values, enums, descriptions, regex constraints, custom casts and validators, and Laravel validation rules. Casts and custom callbacks disable automatic caching when their semantics cannot be safely fingerprinted.
Raw JSON Schema arrays are accepted through as($schemaArray) using a deliberately bounded, locally validated subset. Unknown keys are rejected. Unsupported schema references/combinators fail explicitly rather than being silently ignored. Schema reference.
Make numbers and dates usable
- Money is stored in integer minor units:
264.00becomes26400, avoiding floating-point ledger arithmetic. - Bangla digits can normalize into standard numeric values.
- Date and text normalization help reconcile common document representations.
- Built-in invoice checks compare subtotal, tax, discount, total and compatible line-item arithmetic.
- Validation helpers cover BD phone formats, Luhn, IBAN and passport MRZ checks where applicable.
Supply the right currency and application rules before posting financial records.
Results your application can act on
$result->text(); // Combined page text $result->data(); // Structured values $result->pages(); // Page objects, engines, words and confidence $result->tables(); // Extracted table objects $result->documentType(); $result->language(); $result->metadata(); $result->confidence(); $result->engineSummary(); $result->usedAi(); $result->cost(); // USD, tokens and latency usage $result->warnings(); $result->trace(); $result->fromCache();
A field carries its review context:
$field = $result->field('total'); $field->value; $field->confidence; $field->page; $field->engine; $field->source_quote; $field->validated;
Source quotes support textual grounding where available. They do not prove authenticity or eliminate semantic errors. Missing fields can remain unresolved instead of being invented.
Export and integrate
$json = $result->toJson(); $csv = $result->toCsv(); $markdown = $result->toMarkdown(); $html = $result->toHtml(); $result->save(storage_path('app/private/extraction.json')); // Your application supplies an ocr_data column/cast on this model. $result->save($model);
Results implement Laravel Arrayable/Jsonable and PHP JsonSerializable, with readonly instance properties and Macroable support. Tables provide array/collection, CSV and Markdown exports. Check recipes for application-owned Eloquent mappings; avoid mass-assigning arbitrary extracted values.
Queues, batches and streaming
Move work out of the web request
AiOcr::file(storage_path('app/private/upload.pdf')) ->as('invoice') ->onQueue('ocr') ->trackingId('invoice-import-2026-1042') ->dispatch(); $progress = AiOcr::progress('invoice-import-2026-1042');
php artisan queue:work --queue=ocr
Queued extraction requires a durable local file path visible to every worker, a shared APP_KEY, and a suitable shared cache for progress. Persist uploads/streams first. Keep source files available through retries. Use deleteSourceAfterCompletion() only when the workflow owns that disposable source.
Batch a folder or parallelize pages
$files = glob(storage_path('app/private/imports/*.pdf')); AiOcr::files($files)->using('manual_only')->dispatch(); AiOcr::file($largePdf) ->as('invoice') ->parallel() ->dispatch();
Document batches use Laravel Bus. Page batches merge in original page order before final schema validation and persistence. Configure Laravel's batch table and workers. Each page worker receives a share of the document AI cap. For local CPU parallelism, increase ocr.concurrency to use the bounded Tesseract process pool.
Completion hooks are available through then(), catch() and finally(). Document-batch callbacks follow Laravel Bus signatures; single-extraction callbacks receive the result or exception. Make side effects idempotent across retries.
Consume page results incrementally
foreach (AiOcr::file($file)->manualOnly()->stream() as $page) { // Send progress to your own UI or consume the page. }
Streaming yields pipeline pages before final schema work. Native extraction finishes before the first yield, and final results retain text/trace; this is not a constant-memory promise for arbitrarily large output. Default page limit: 200. A synthetic 300-page regression is documented in performance.
Persistence, history and human review
Persistence is optional. Enable it when you need durable records and the history/review experience:
AI_OCR_STORE=true
php artisan migrate
- ULID document, extraction and page records, plus saved schemas.
- Owner-scoped history and stored-result retrieval.
- Encrypted results and page text by default.
- SHA-based fingerprints and configurable cache/deduplication TTL.
- Optional encrypted original retention for reruns; disabled by default.
- No-text storage mode for deployments that do not need retained page text.
- Human review flags used to calculate reviewed accuracy in analytics.
- Pruning through
ai-ocr:prune; schedule it in your application.
// Enable original retention only when reruns and your retention policy require it. config(['ai-ocr.store.retain_original' => true]); $result = AiOcr::file($file)->ownedBy($customer)->as('invoice')->extract();
HasOcrDocuments exposes ocrDocuments, ocrExtractions and an owned ocr() request. Media models can implement Contracts\MediaSource. Disabling stored text also disables OCR result caching; structured values may still contain sensitive data.
Studio, Filament and HTTP API
Studio
Enable AI_OCR_STUDIO=true and visit /ai-ocr. Outside local development, authorize access in an application service provider:
AiOcr::auth(fn ($request) => $request->user()?->can('manage-documents') ?? false);
You can also define the viewAiOcr Gate or configure role/email allowlists.
| Screen or action | What operators get |
|---|---|
| AI Providers | Password key entry, models, enabled/default controls, configuration status and shared AI Hub settings |
| Playground | Drag/drop upload, preview, strategy, provider/model, type/schema, languages, DPI, sync/queue/page-parallel controls |
| Extraction viewer | Source beside text/JSON, engine badges, confidence-colored words and JSON download |
| Trace | Processing steps, quality decisions, warnings and usage information |
| Compare | Manual, AI and consensus candidates for the same upload; AI calls may incur costs |
| Schema builder | Field definitions, required flags, apply-to-Playground and saved templates |
| History | Owner-scoped filtering, result retrieval/export, review flags and rerun using a different strategy |
| Doctor | Binary paths, installed languages, optional dependencies, provider readiness, queue and limits |
| Analytics | Document count, spend, confidence, fallback rate, per-type reviewed accuracy and estimated AI-only comparison where data supports it |
Saved templates need persistence and an authenticated owner. Reruns need retained originals. Analytics savings extrapolate observed AI cost; they are estimates, not measured labor savings. Studio ships with bundled CSS/plain JavaScript, light/dark mode and no external asset CDN requirement.
Optional Filament 3/4
Install Filament in your host application and register the plugin on your panel:
->plugin(\ImranDevBd\AiOcr\Filament\AiOcrPlugin::make())
The plugin provides authorized Studio navigation; when persistence is enabled it registers an owner-scoped extraction resource and statistics widget. Its upload action opens Studio. Filament is feature-detected and is not a hard runtime dependency.
Opt-in HTTP API
config(['ai-ocr.api.enabled' => true]);
Default API middleware uses api, auth:sanctum and throttling. Install/configure Sanctum in the host app, or supply your own authenticated middleware policy.
| Method | Default route | Purpose |
|---|---|---|
| POST | /api/ai-ocr/extract |
Multipart upload; sync result or queued response |
| GET | /api/ai-ocr/extract/{id} |
Queued progress/result polling |
| GET | /api/ai-ocr/extractions/{id} |
Owner-scoped stored result |
| POST | /api/ai-ocr/extractions/{id}/rerun |
Rerun a retained original with an allowed strategy |
| POST | /api/ai-ocr/extractions/{id}/review |
Save the owner's correctness review |
Upload fields include file, strategy, type, languages[], dpi, provider, model, JSON schema, queued and parallel. A queued response returns an ID and poll_url. Stored reads, reviews and reruns require persistence.
Events and signed webhooks
Listen for extraction start/completion/failure, page extraction, quality-gate failure, AI fallback and budget-block events from ImranDevBd\AiOcr\Events.
Optional webhooks send metadata only, with timestamp, event ID and HMAC SHA-256 signature. Configure a public HTTPS target and secret; use Support\WebhookVerifier for signature/age/replay checks. Receivers should handle retries idempotently. Webhook recipe.
Control AI spending and data exposure
Spend AI calls where they can help
$estimate = AiOcr::estimate($file); $result = AiOcr::file($file) ->provider('openai') ->model($approvedModel) ->maxCost(0.05) ->cache(3600) ->forJob('invoice-import') ->extract();
- Readable native text avoids AI page transcription in the automatic strategy.
- Local OCR gets a chance before cloud fallback.
- Vision is restricted to failed pages; missing-field recovery uses restricted text-only requests.
- Configure allowed providers, fast/strong models and maximum AI pages per document.
- Model-price preflight and AI Hub budgets can block a request before it is sent.
- Unknown cloud-model pricing blocks preflight rather than being treated as free.
- Cache hits report zero new cost/tokens. Keys include owner, document, schema/options, model settings and prompt version.
noCache()disables OCR cache and persistent deduplication for that request.
maxCost() is an estimated preflight control, not an atomic billing guarantee. Provider tokenization, retries and concurrent workers affect real spend. Use provider-side spending limits for a contractual cap, and inspect actual result usage.
Choose a privacy boundary
| Policy | Behavior |
|---|---|
manualOnly() / AI_OCR_AI=false |
No AI requests from the extraction pipeline |
Type-specific local_only |
AI limited to approved literal loopback/private Ollama endpoints; bd_nid defaults to this policy |
| Provider allowlist | Only configured, ready, allowed providers can receive a request |
send_text_only |
Blocks image transmission |
redact(['nid', 'phone']) |
Reversible text tokens; vision blocked because regex cannot safely redact image pixels |
| Encrypted cache/storage | Laravel encryption protects cached responses and enabled result/text persistence |
| No-content logging | OCR scrubs Hub content previews while retaining approved spend metrics |
OCR stores no provider keys. The verified Hub v1.6 settings backend is not encrypted upstream; secure the Hub deployment separately. Local-only inference still depends on the security of your local inference service and infrastructure.
Input and execution safeguards
Content-based MIME checks, byte/page/pixel limits, ZIP expansion/entry/depth limits, safe XML parsing, private random temporary paths, shell-free process arguments, binary allowlists, time/output bounds, available Linux prlimit, DNS-pinned public downloads, disabled redirects/proxies, owner scoping, CSRF and authenticated API defaults form the package's main controls.
They do not replace OS/container isolation or current system binaries. See the threat model and security reporting policy.
Configuration at a glance
Publish ai-ocr-config to customize config/ai-ocr.php. The complete configuration guide explains defaults and dependencies.
| Setting | Default | Why you might change it |
|---|---|---|
strategy |
auto |
Choose a different default workflow |
limits.max_size_mb / max_pages |
25 / 200 | Match your upload/worker capacity |
limits.max_pixels |
60,000,000 | Bound raster work |
ocr.languages / dpi |
['eng'] / 300 |
Match document script and scan quality |
ocr.concurrency |
1 | Increase bounded local OCR parallelism |
quality.min_score / min_chars_per_page |
.75 / 25 | Tune against representative fixtures |
ai.max_pages_per_document |
30 | Limit image fallback breadth |
ai.max_cost_per_document |
$0.25 | Set the default estimated AI spending ceiling |
ai.cache_ttl |
86,400 sec | Control repeat-processing reuse |
ai.image_max_side / image_format |
1800 / JPEG | Balance image payload size and readability |
privacy.policies.bd_nid |
local_only |
Preserve the intended identity-document boundary |
store.enabled / retain_original |
false / false | Enable history and optionally reruns |
store.encrypt / store_text |
true / true | Define retained result/text policy |
queue.queue |
ocr |
Route work to dedicated workers |
api.enabled / webhooks.enabled |
false / false | Explicitly enable application integrations |
Environment shortcuts include AI_OCR_STRATEGY, AI_OCR_AI, AI_OCR_STORE, AI_OCR_STUDIO, AI_OCR_ROLES, AI_OCR_EMAILS and AI_OCR_WEBHOOK_SECRET. Configure the remaining keys through Laravel config, cache it in production, and restart queue workers after changes.
Fluent API reference
| Task | Methods |
|---|---|
| Begin a request | file($input), url($url), files($inputs) |
| Choose processing | using($strategy), manualOnly(), withoutAi(), aiOnly(), requireAi() |
| Control AI | provider($name), model($name), maxCost($usd), forJob($tag) |
| Scope input | disk($name), pages('1-3,7'), languages([...]), dpi(300), preprocess([...]) |
| Define output | as($typeOrSchema), autoDetectType(), validate($rules), minConfidence(.8) |
| Privacy and reuse | redact([...]), cache($seconds), noCache(), ownedBy($model) |
| Complete synchronously | extract(), text(), stream() |
| Background work | onQueue($name), trackingId($id), parallel(), dispatch() |
| Hooks and failure handling | safe(), tap($callback), then(), catch(), finally() |
| Source lifecycle | deleteSourceAfterCompletion() for disposable queued sources |
| Manager utilities | doctor(), estimate($input), progress($id), auth($callback), registerType($type), extend($name, $resolver), driver($name), fake() |
files() returns a BatchRequest with its own smaller API: using(), completion hooks, extract() and dispatch(). It does not expose every single-file option. See the source ExtractionRequest and BatchRequest.
Artisan commands
| Command | Purpose |
|---|---|
ai-ocr:install |
Publish configuration/migrations, run diagnostics and print next steps |
ai-ocr:doctor |
Inspect binaries, languages, provider readiness and required extensions |
ai-ocr:extract |
Extract a file from the command line |
ai-ocr:estimate |
Inspect preflight AI cost estimates |
ai-ocr:benchmark |
Measure fixture throughput, cost and confidence |
ai-ocr:install-langs |
Print platform-specific language installation guidance |
ai-ocr:prune |
Remove records/sources according to retention rules |
ai-ocr:types |
List registered document types |
php artisan ai-ocr:extract /private/invoice.pdf --strategy=auto --type=invoice --lang=ben,eng --json php artisan ai-ocr:extract /private/invoice.pdf --no-ai php artisan ai-ocr:estimate /private/invoice.pdf php artisan ai-ocr:benchmark /private/fixtures php artisan ai-ocr:install-langs ben eng php artisan ai-ocr:prune --days=90
Benchmarking is manual-only unless you explicitly pass --ai, which can incur charges. It reports measured processing throughput and usage; it cannot calculate true extraction accuracy without annotated ground truth. The language command prints instructions rather than silently installing operating-system packages.
Make it fit your application
- Custom document types: subclass
Types\DocumentType, define a schema, normalize values, add Laravel rules/cross-checks, thenAiOcr::registerType(...). Guide. - Custom drivers: bind native extraction, OCR, rasterization, AI or readiness contracts in Laravel's container. Guide.
- Named extensions: use
extend()anddriver()for application-owned integrations; a named extension does not automatically replace a pipeline contract. - Eloquent integration: map approved values into your own models or use the ownership/media hooks. Recipes.
- Review workflow: use field validation, confidence, warnings and evidence to choose what can proceed and what needs a person.
- Your product UI: call the PHP API or protected HTTP API from the admin/customer experience you already have. Studio is optional.
Testing and validation
Test your consuming application without running OCR
AiOcr::fake(); // Run the application behavior that requests extraction. AiOcr::file('sample')->extract(); AiOcr::assertExtracted(); AiOcr::assertExtractedCount(1); AiOcr::assertNotUsedAi();
Supply an ExtractionResult or a list of results to fake() for realistic application tests. Additional assertions include assertUsedAi() and assertSentToAi($callback).
Run package checks
composer install composer check # With Xdebug or PCOV; load Imagick/GD to exercise optional image branches. vendor/bin/phpunit --coverage-clover coverage.xml php tools/check-coverage.php coverage.xml # Build the production source ZIP and checksum. php tools/build-release.php
| Locally verified check | Result |
|---|---|
| Main suite with optional image codecs | 103 tests, 383 assertions; one optional Filament skip |
| PHP 8.2/8.3/8.4 × supported Laravel 10/11/12/13 combinations | All 11 combinations pass |
| Fresh Laravel application installs | Versions 10, 11, 12 and 13 pass discovery, Doctor and type listing |
| Optional Filament integration | Versions 3 and 4 pass |
| PHPStan / Larastan | Level 8, zero errors on the current toolchain |
| Pint and Composer metadata | Passed |
| Pipeline coverage | 90.33% |
| Quality / Schema / Normalizers / AI driver coverage | 97.73% / 97.12% / 97.03% / 97.98% |
The default tests block stray HTTP calls and use AI Hub fakes. An opt-in live suite is separate and requires AI_OCR_LIVE=true; it can spend money using configured providers. No billable live-provider calls were made for these validation results.
Historical Laravel 10/11 tests use isolated Composer advisory overrides for dependency resolution. Consumer security defaults remain enabled. The CI workflow defines matrix/coverage/Filament jobs. The first hosted run was blocked before execution by an account billing lock; the release validation reported here was performed locally. Full evidence and reproduction notes.
Frequently asked questions
Does every document need an AI API key?
No. Native parsing and local Tesseract work without one. In automatic mode, unresolved pages/fields remain visible through warnings when no allowed AI provider is available. requireAi() is available for workflows that explicitly need AI.
Is local extraction free?
Native parsing and Tesseract do not incur model API charges. Your server CPU, memory, storage, setup and maintenance still have costs. Optional AI requests are billed by your configured provider.
Does it handle Bangla and legacy Bangla PDFs?
It supports Bangla Tesseract language packs, digit/date normalization and expected-script/garbled-text heuristics. Weak legacy text layers can trigger raster OCR or allowed AI recovery. It is not a universal Bijoy converter, and difficult scans still need review.
Can it read handwriting and complex tables?
A handwritten-note type and policy-controlled vision fallback are available. Results depend on legibility, language, model capability and layout. Complex tables may need a custom schema, AI assistance and human correction. No universal accuracy percentage is claimed.
Can I use OpenAI, Gemini, Claude or Ollama?
Provider integration comes through AI Hub, with credentials, model names and pricing configured there. Choose an appropriate ready provider and model for the requested text/vision capability. OCR applies its own allowlists/privacy checks and disables Hub's unrestricted automatic failover. See the verified integration guide for v1.6 behavior.
Will sensitive documents leave my server?
Manual-only mode makes no AI requests. Automatic mode can send permitted failed-page images or text for repair. Local-only policies restrict inference endpoints, and redaction blocks vision. Choose the policy for each document class before enabling cloud fallback.
Does confidence mean the value is correct?
No. Confidence is a heuristic, and a source quote only supports grounding. Combine field checks, cross-checks, representative fixtures and human review. Reviewed accuracy in Studio is based on recorded human correctness flags.
Can it process hundreds of pages?
The default cap is 200 pages. Limits can be raised after capacity testing; a 300-page synthetic memory regression is included. Raster images are released as work advances, but text and trace accumulate in results. Use dedicated workers, selected pages and batches for large workloads.
What happens when a binary or optional library is missing?
Doctor reports available capabilities. The pipeline uses supported paths and returns diagnostic warnings or typed failures when the requested capability is unavailable. Reliable PDF text/raster work needs the recommended binaries; no tool can recover every document from missing dependencies.
Can I build a multi-tenant SaaS or charge for document processing?
The MIT license permits commercial use subject to its terms. Owner-scoped storage, API routes and usage tags are building blocks. Your host app must implement tenant authentication, quotas, billing, worker/storage isolation and customer retention policies. The package does not bundle a SaaS billing system.
How do I upgrade or publish a stable version?
Review CHANGELOG, preserve local privacy policies when publishing config, run migrations/tests and verify model pricing/capabilities. Version 1.0.0 establishes the initial stable release. Future compatibility changes will be recorded in CHANGELOG. The release guide explains identity checks, validation, tagging and Packagist submission.
Documentation
| Guide | What it covers |
|---|---|
| Installation and binaries | Linux, macOS, Windows, Docker, VPS/FastPanel, Poppler and language packs |
| Visual AI provider setup | Inline API key/model forms, Hub permissions and shared settings |
| Configuration | All configuration groups, defaults and environment shortcuts |
| AI Hub integration | Verified dependency source, readiness, image transport, settings and adapters |
| Schemas and evidence | Fluent/raw schemas, rules, callbacks and grounded fields |
| Custom types | Application-specific document models and cross-checks |
| Custom drivers | Container contracts and extension points |
| Quality gate | Scoring, garbled text and fallback decisions |
| Security and privacy | Trust boundaries, input controls, logging and residual risks |
| Performance and cost | Memory, concurrency, caching, budgets and measurement |
| Application recipes | Eloquent, invoices, NID, receipts, imports, Filament and webhooks |
| Validation report | Executed tests, compatibility, coverage and limitations |
| Implementation boundaries | Deliberate differences and deployment-dependent behavior |
| Complete file tree | Delivered source inventory |
| Release guide | Local checks, source archives, Git tags and Packagist steps |
Contributing, support and license
Bug reports are most useful with a synthetic, non-sensitive reproduction, dependency versions, Doctor output and the relevant warnings/trace. Never attach real identity documents, credentials or confidential customer files to a public issue.
If this package helps your Laravel project, a GitHub star helps other developers discover it.
Build document workflows that give your team more time for the work that needs them.
Created and maintained by Imran Ahmed · me@imrandev.bd
🤝 Let's Connect & Collaborate
I am open to Senior Remote Full-Stack Roles, AI Platform Architecture Contracts, and Enterprise Technical Advisory.
Timezone: UTC+6 (Dhaka / Rangpur, Bangladesh) — Flexible overlap with US, UK, and European business hours.
Delivery Mode: Async-ready, Slack, Discord, Jira, GitHub, and production-first accountability.
| Channel | Address / Handle | Quick Action |
|---|---|---|
| 🌐 Primary Portfolio | imrandev.bd | Visit Site ↗ |
| 📦 Packagist Packages | packagist.org/packages/imrandevbd/ | View Packages ↗ |
| 💼 LinkedIn Profile | linkedin.com/in/imranbru99 | Connect ↗ |
| 🐙 GitHub Profile | github.com/imranbru99 | Follow ↗ |
| 💬 WhatsApp Direct | +880 1576-918420 | Chat Now ↗ |
| 📧 Direct Email | me@imrandev.bd | Send Email ↗ |
| 🐦 X (Twitter) | @imrandev_bd | Follow ↗ |
| 📺 YouTube Tech | @ImranDevBD | Subscribe ↗ |



