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

Pdf Pack Laravel Package

1tomany/pdf-pack

A PHP library for rasterizing PDF pages and extracting text via Poppler, with metadata reading, JPEG/PNG output, and a flexible client interface. Requires pdfinfo, pdftoppm, and pdftotext.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the system dependency first, not the Composer package. pdf-pack is a thin wrapper over Poppler's CLI binaries — it contains no PDF parsing code of its own. If pdfinfo, pdftoppm, and pdftotext aren't on $PATH, everything compiles and nothing works.

# macOS
brew install poppler
# Debian/Ubuntu
apt-get install poppler-utils

Verify with which pdfinfo pdftoppm pdftotext before touching Composer. In a Laravel app this belongs in your Dockerfile base image, pinned to a version, plus a php artisan pdf:health command that shells out and fails loudly — otherwise the failure surfaces in a queued job days later.

2. Add the package and pin it.

composer require 1tomany/pdf-pack

Pin to a minor version. The 0.5 → 0.7 release line renamed request/action/response classes more than once (ExtractPdfAction → ConvertPdfAction in 0.7.1), so un-pinned installs will break snippets you find in blog posts and older READMEs.

3. Read these three files, in this order.

  • examples/direct.php — the canonical happy path; it is the real documentation.
  • src/Contract/Client/ClientInterface.php — the three methods you'll actually call (metadata/page count, rasterize, extract text).
  • src/Client/Poppler/PopplerClient.php — how requests map onto Poppler flags, which is where all the surprises live.

4. There is no Laravel package. No service provider, no auto-discovery, no facade, no config stub. v0.9.0 merged the Symfony bundle into the repo, which is a useful blueprint but does nothing for you at runtime. Register the client yourself:

// app/Providers/PdfPackServiceProvider.php
public function register(): void
{
    $this->app->singleton(ClientInterface::class, function () {
        return new PopplerClient(); // check examples/direct.php for constructor args
    });
}

First use case: feed a PDF into an LLM prompt. Extract text from every page, concatenate, done:

$client = app(ClientInterface::class);

foreach ($client->extract(/* file request for pages 1..N */) as $page => $text) {
    $prompt .= "--- Page {$page} ---\n{$text}\n";
}

The return value is a \Generator, so nothing is extracted until you iterate. Consume it fully, or write each page out as you go.

Implementation Patterns

Wrap the client in one application-level action. Controllers, jobs, and Artisan commands should never touch PopplerClient directly. One class — ExtractPdfText, RasterizePdfPages — keeps the request-object churn contained when you upgrade.

Probe metadata before doing expensive work. Page count is cheap; rasterization is not. A typical routing pattern:

  1. Read page count via the metadata call.
  2. Try text extraction on a sample page.
  3. If the text is empty, the PDF is scanned — either rasterize and hand off to OCR (Tesseract/Textract), or reject with a clear domain error.
  4. Otherwise extract text page-by-page and chunk for the model.

This one check prevents the most common production complaint: "we rasterized a 400-page born-digital PDF and the job timed out."

Fan out per page-range in queued jobs. Generators pair naturally with Laravel queues. Chunk by page range (50–100 pages), one job per chunk, then aggregate:

final class ExtractPdfTextChunk implements ShouldQueue
{
    use InteractsWithQueue, Queueable;

    public int $timeout = 300;
    public int $tries = 2;

    public function __construct(
        public string $path,
        public int $firstPage,
        public int $lastPage,
    ) {}

    public function handle(ClientInterface $client): void
    {
        $pages = $client->extract(/* request for $this->firstPage..$this->lastPage */);

        foreach ($pages as $page => $text) {
            Storage::disk('local')->put(
                "pdfs/{$this->id}/{$page}.txt", $text
            );
        }
    }

    public function middleware(): array
    {
        return [new WithoutOverlapping($this->path)];
    }
}

WithoutOverlapping matters: Poppler CLI invocations on the same source file from parallel workers are not safe to assume thread-safe.

Stream to storage; never accumulate in the job. Each page's bytes are held in memory by design. Persist immediately inside the loop and let the variable go out of scope — don't build an array of pages and write at the end.

Use data: URIs only for small previews. The optional 1tomany/data-uri integration (SmartFile) is convenient for sending a thumbnail to an API or an admin preview. For anything user-facing at scale, write to a disk and serve a signed URL.

Fake the interface in tests. This is the best architectural property of the package and the reason to prefer the ClientFactory/ClientInterface path over direct instantiation. Your test fake returns a generator over fixture strings; your suite never needs Poppler installed, which keeps CI fast and lets you run tests on machines without poppler-utils.

Expose it as an Artisan command for backfills. php artisan pdf:extract {path} --pages=1-20 is the fastest way to debug behaviour against a real document without going through an upload flow.

Gotchas and Tips

Failures are runtime, not boot-time. pdfinfo on a corrupt, password-protected, or truncated PDF exits non-zero and writes to stderr. The library surfaces this through Symfony\Component\Process, which surfaces as a ProcessFailedException-shaped error — not a domain exception. Catch it at your action boundary and translate it into something your Handler can report meaningfully. Encrypted PDFs in particular should be detected and rejected up front.

Page numbering is a classic off-by-one. Poppler's CLI flags are 1-based; request objects in the library have changed shape across releases. Confirm the convention for the version you installed with a two-page fixture before shipping anything that says "page 3."

Timeouts and memory are your problem, not the package's. Set $timeout on jobs and --memory / memory_limit on workers. A single 300 DPI rasterized page can be several megabytes of raw image data. Also configure the Process timeout if the client exposes it — the default may be too generous for you.

Temp files pile up. pdftoppm writes intermediate files. Under concurrency, give each invocation a unique prefix or output directory, and schedule cleanup; a leaking temp directory is the most common silent disk-full incident with this class of wrapper.

Scanned PDFs return empty text, not an error. There is no signal other than an empty string. Treat "all pages empty" as a distinct outcome and branch to OCR rather than storing blank text as if extraction succeeded.

Version churn in the request/response layer. file → filePath (0.3.1), ImageType default flipped to PNG then reverted (0.3.3), ExtractPdfAction → ConvertPdfAction (0.7.1). Contract classes are still the least stable surface. Depend on ClientInterface and the library's value objects, keep your own DTOs at the boundary, and upgrades become mechanical.

Expect noisy releases. Since v0.9.0 the Symfony bundle lives in this repo, so bundle-only changes (container wiring, config schema) bump the version of the core client you depend on. Don't treat semver bumps as meaningful risk signals here; read the changelog.

The Symfony bundle will land in your vendor tree. It's inert in Laravel, but it may pull Symfony config/bundle classes as dependencies. Fine, just know what you're auditing.

Low adoption is a real consideration. Four stars, zero dependents. That's not disqualifying for a 300-line wrapper, but it argues for wrapping it in your own PdfExtractor interface from day one so swapping to a cloud API (Textract, Google Document AI) or a native PHP library is a one-class change rather than a codebase-wide refactor.

Extension points worth knowing: ClientInterface is the seam for fakes and alternative backends; ClientFactory is the seam for configuration-driven client selection (e.g., local Poppler in dev, remote service in production); the SmartFile/data-uri path is optional and only present if you install 1tomany/data-uri yourself.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource