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 Optimizer Laravel Package

mostafaznv/pdf-optimizer

PDF Optimizer is a PHP and Laravel package that compresses and optimizes PDF files with Ghostscript to significantly reduce file sizes. It offers a fluent API, logger support, queue-based async processing, and flexible input via file paths, UploadedFile instances, or disk storage.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the system dependency first, not the package. The PHP side is trivial; Ghostscript is the actual engine and the usual source of first-day failure.

# Debian/Ubuntu base image
apt-get install -y ghostscript
gs --version   # must succeed on every web node AND every queue worker

composer require mostafaznv/pdf-optimizer
php artisan vendor:publish --tag=pdf-optimizer-config

2. Publish and read config/pdf-optimizer.php before writing any code. It is short and it tells you the two things that matter operationally: where the gs binary is expected to live, and what the default timeout/temp path are. Point binary at an absolute path via env (GS_BINARY=/usr/bin/gs) rather than relying on $PATH — PHP-FPM and queue workers frequently have a different PATH than your shell, and "works in tinker, fails in the worker" is almost always this.

3. First use case: shrink an uploaded PDF before it lands in storage.

use Mostafaznv\PdfOptimizer\PdfOptimizer;

$result = PdfOptimizer::init()
    ->input($request->file('document'))   // UploadedFile is accepted directly
    ->disk('local')                       // Laravel disk, optional
    ->output("invoices/{$invoice->id}.pdf")
    ->optimize();

The fluent builder exposes most Ghostscript flags, so the API surface is wide. Treat the GitBook docs as canonical for exact method names and enum cases; the snippets here show the shape of the workflow, not a complete method reference.

4. Learn one debugging method on day one: ->command(). It returns the fully-assembled Ghostscript command string without executing it. Being able to paste that into a shell on the failing host collapses hours of "why did it fail in production" into one command. Build the habit immediately — it is the single most valuable affordance in the package.

5. Write one throwaway test against your own corpus before committing to the design. Take ten real PDFs from your app (not the package's fixtures), run them through, and eyeball the output. Size reduction is not the metric that matters; legibility of a scanned invoice is.

Implementation Patterns

Wrap it in an application service, never call the facade from controllers. The package is a thin orchestration layer over a binary; you want exactly one place in your codebase that knows that. A small PdfCompressor class behind an interface gives you a one-week swap if Ghostscript licensing or runtime fit forces a change, and gives you a single choke point for logging, metrics, and fallback behavior.

interface PdfCompressor
{
    public function compress(CompressionRequest $request): CompressionResult;
}

Make CompressionResult a value object carrying path, bytesBefore, bytesAfter, durationMs, and succeeded. Callers get a decision ("serve optimized or serve original"), not a boolean.

Never overwrite the input file. Write to a distinct output path and store both. Compression is lossy and occasionally produces a worse or malformed file; without the original you have no fallback and no re-run. The storage cost is a rounding error compared to the support cost of a corrupted invoice.

Push everything non-trivial to a queue. The package's queue support is the correct default for anything above a few hundred KB, because Ghostscript is CPU-bound and will pin a core for seconds. The pattern:

final class OptimizeInvoicePdf implements ShouldQueue
{
    public function __construct(public int $invoiceId) {}

    public function handle(PdfCompressor $compressor): void
    {
        // build request, call compressor, persist result on the model
    }
}

Use Bus::batch() for bulk imports (re-uploading an archive, backfilling historical documents) so you get progress and per-item failure tracking for free. Give the batch a catch callback that records failures rather than silently leaving documents unoptimized.

Pick compression settings per document class, not globally. Ghostscript's settings presets trade fidelity for size in very different ways — a screen-oriented preset is fine for a web-preview thumbnail and catastrophic for a legal filing. Model this as an enum in your domain (DocumentProfile::Preview|Standard|Archive) that maps to package settings in one place. Then you can tune without touching call sites.

Instrument the pipeline as if it were an external API call, because it is one. Log the command string, wall-clock duration, input/output byte counts, and the ratio. Alert on a ratio that collapses to ~1.0 (compression silently doing nothing) and on duration p95 creeping up. A sudden shift in either is usually a Ghostscript version bump on a rebuilt image, not a code change.

Treat the binary as a health-checked dependency. Add one artisan command or startup probe that runs gs --version and asserts against a pinned expected version. Fail loudly at deploy time rather than discovering it when the first user uploads.

In tests, skip rather than fail when Ghostscript is absent, and keep a small fixture corpus of three or four representative PDFs (vector text, image-heavy scan, multi-page). Assert on byte-size reduction and on the output being parseable — not on exact output hashes, which vary across Ghostscript versions unless you pin hard.

Gotchas and Tips

Licensing is a build-time decision, not a code detail. The package is MIT; Ghostscript is AGPL or commercial. If you ship this inside a network-facing commercial product, run the AGPL question past legal before you build the queue pipeline around it. This is the one item in the whole evaluation that can invalidate the work retroactively.

"Ghostscript not found" surfaces as an opaque process failure. The package shells out; a missing binary, a non-executable path, or a PATH mismatch in FPM produces an unhelpful error. Centralize construction of the optimizer in a factory that reads the absolute binary path from config, and test it on each runtime you deploy to.

Quality degradation is real and corpus-specific. Vector-generated PDFs (dompdf, wkhtmltopdf, LaTeX) survive aggressive settings well. Scanned, image-heavy, or form-bearing PDFs can become illegible or lose interactive elements. Never infer quality from the file size drop. Sample output from each document class, and keep a "don't compress" escape hatch for classes that fail review.

Worker timeout must exceed package timeout, comfortably. A job that kills a Ghostscript process mid-write leaves a truncated file behind. Set the package/process timeout below queue:work --timeout, and remember that --timeout and retry_after have their own classic mismatch failure mode.

Temp-file hygiene and concurrent invocations. Ghostscript writes scratch files; N workers on one node multiply that. Give the package a dedicated temp directory on a volume with headroom, monitor disk, and don't share a single temp path across containers if the temp dir is tiny (many Lambda-ish base images ship a small /tmp).

Output directories are created by the package, but only for paths it knows about. If you use disk storage and a nested output path, verify the directory exists in your configured disk and that the disk's root is writable by the FPM/worker user — silent permission failures look like optimization failures.

Don't let users influence the flag set. The fluent builder maps to shell arguments. Any feature where a user picks a "compression level" must be a whitelisted enum in your code, never a pass-through string.

Pin the Ghostscript version in your image and rebuild on a schedule. Ghostscript 9 vs 10 and even minor bumps shift output and runtime behavior. If anyone downstream needs byte-reproducible output (archival, dedupe, audit hashes), pinning is mandatory rather than good practice.

Windows is untested upstream — assume Unix-only and check your CI matrix before someone adds a Windows runner.

Escape hatch when a job fails: call ->command(), reproduce on the worker host, and compare against a working local run. That plus a before/after file size log answers roughly 90% of incidents with this package. Everything else is usually disk space or permissions.

Extend by composition, not by patching. The package exposes config, custom flags, logger injection, timeout, temp path, and success/error handlers — that's enough surface to adapt behavior. Don't fork it; it's a small single-maintainer project, and a fork guarantees you own the maintenance burden. If you need behavior it doesn't offer, wrap the interface and call Ghostscript yourself for that one case.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log