spatie/pdf-to-image
Convert PDF pages to images with this simple PHP class built on Imagick and Ghostscript. Save as jpg, jpeg, png, or webp, render any page, and check page counts. Requires PHP 8.2+.
What the package actually is. spatie/pdf-to-image is a thin, opinionated PHP wrapper (~one public class, Spatie\PdfToImage\Pdf) over two native dependencies: ImageMagick's imagick extension and the Ghostscript gs binary. The package itself owns no rendering logic — it composes command flags, manages page selection, and normalizes output formats. That distinction drives most of the assessment below: Composer installs a convenience API, not a capability.
Architecture fit. Strong fit for Laravel monoliths and job-queue-driven pipelines that already shell into native tooling. It is deliberately synchronous and stateless: construct with a local path, chain configuration, call save(). That maps cleanly onto a queued job, a console command, or a Filament/Nova action. It is a poor fit for serverless/read-only-container runtimes (Lambda, Vercel, Cloud Run variants) where you cannot install Ghostscript or mutate policy.xml, and for multi-tenant environments where you cannot trust the PDFs you receive. The v3 removal of remote-URL loading is a security posture change worth noting — it is a feature for us, but it means any legacy code path fetching https://... PDFs breaks loudly at upgrade.
Capability surface. Adequate for the common product needs: page count, single-page render, multi-page render, DPI control, width/height and thumbnail sizing, JPG/PNG/WebP output, compression quality, background color, layer merging, and password-protected PDFs (ImageMagick 7 only, with an explicit PasswordNotSupported guard on IM6). The API returns string for single-page and array for multi-page, which is a mild ergonomic wrinkle — callers must type-check or normalize before persisting paths.
Integration feasibility. Low code effort, moderate environment effort. composer require is trivial; the real work is containerization or host provisioning: Ghostscript, ImageMagick 7, the imagick PHP extension, correct policy.xml (PDF coder read|write rights, plus relaxed width/height resource limits for ultra-wide PDFs), and — the classic trap — env[PATH] in php-fpm.conf so FPM workers can resolve gs. The README documents this failure mode explicitly because it is the single most common production incident: CLI jobs work, browser-triggered renders throw FailedToExecuteCommand 'gs'.
Technical risk. Concentrated almost entirely in the native layer, not the PHP layer:
| Risk | Severity | Notes |
|---|---|---|
| Ghostscript/ImageMagick CVE exposure | High | Untrusted PDFs are a well-known RCE vector (ImageTragick lineage, repeated gs sandbox bypasses). Mitigation is process isolation, not library choice. |
| Environment drift between dev/CI/prod | Medium | IM6 vs IM7 changes password support and rendering; policy.xml differs by distro. |
| Resource exhaustion | Medium | Large/high-DPI PDFs inflate memory and disk; a 300 DPI render of a 200-page document will not be polite. |
| Breaking changes across majors | Medium | v3 was a substantial API rewrite (typed enums, renamed methods, dropped URL loading, PHP 8.2 floor). |
| Library abandonment | Low-Medium | 1.4k stars, active Spatie stewardship, MIT, ~93% test coverage per v3 notes — but the maintainer burden sits with few hands. |
Key questions for the engineering team.
imagick already compiled into our PHP images? If not, that's a base-image change with a rebuild-and-redeploy cycle across all services.Stack fit. Native to Laravel: PSR-4 autoloading, no service provider required, no config publishing, no facade. It composes with the standard Laravel toolkit without friction — wrap calls in a ShouldQueue job, store output through the Storage facade, gate access with policies, surface progress via queues/events. For non-Laravel or mixed estates, plain Composer works, but the ergonomics assume a framework that gives you queues, storage abstraction, and process supervision for free.
Prerequisites and provisioning. Treat the runtime image as the primary deliverable:
ghostscript, imagemagick 7, php-imagick, plus fonts (a fontless container silently renders blank or substituted text — a frequent "it works on my machine" gap).policy.xml that permits the PDF coder and raises width/height limits, version-controlled alongside the Dockerfile rather than patched at runtime.env[PATH] set for PHP-FPM so gs resolves from web requests as well as CLI.Migration path. Two distinct migrations to plan separately:
Compatibility. PHP 8.2+ is a hard floor. Confirm every service that will touch the package is on 8.2, not just the one making the call. On the native side, pin ImageMagick major version in the image — IM6 vs IM7 changes password behavior and can shift anti-aliasing. If password-protected PDFs are in scope, gate the feature on Pdf::supportsPasswordProtectedPdfs() rather than assuming.
Sequencing.
policy.xml + FPM PATH fix, validated in a staging environment that mirrors prod.PdfThumbnailer) wrapping the package: normalizes single-vs-array returns, enforces DPI/quality defaults, handles temp files and cleanup, emits metrics. This wrapper is the actual integration surface — no direct package calls in controllers.Maintenance. Two maintenance surfaces, and the PHP one is the cheap one. Composer updates are routine; the native stack is where toil lives — base-image rebuilds for Ghostscript security patches, policy.xml drift between environments, extension upgrades that require a PHP rebuild. Assign explicit ownership of the container image, because "someone else's Dockerfile" is how gs silently falls three CVE cycles behind. Track the package's release cadence and read changelogs before minor bumps; this project has historically shipped behavioral fixes inside patch releases (layer flattening, transparency, page-count correctness).
Support. The package has no support contract — MIT, community issues, maintainer goodwill. The realistic support model is internal: a runbook covering the three recurring failures (FPM can't find gs; policy.xml blocks the PDF coder; ultra-wide PDF hits resource limits), plus an escalation path to "render fails, degrade gracefully" rather than a page. Do not plan on upstream responsiveness for production incidents.
Scaling. Vertically bound and CPU-hungry. Rendering is single-threaded per invocation and memory scales with DPI × page area; a 300 DPI render of a dense A4 page is meaningfully more expensive than 72 DPI. Scale out with queue workers, not with concurrency inside PHP. Practical levers, in order of impact:
(document_id, version, page, format, size). This is the single largest cost reduction available and should be non-negotiable.Failure modes to design for explicitly.
memory_limit and Ghostscript resource policy deliberately; prefer a clean failure over an OOM that takes down other jobs on the worker.PasswordNotSupported. Detect capability at startup and either feature-gate or fail with a clear user-facing message.selectPages() into a directory can leave a subset on failure. Validate expected file count before marking the job complete, or write to a temp prefix and atomically promote.Ramp-up. For a Laravel-fluent team, the PHP API is a half-day to learn — the README is complete and the surface is small. The native stack is where onboarding cost concentrates: expect the first engineer to lose 1–2 days to FPM PATH, policy.xml, and container font installation. Mitigate by encoding all of it in the base image and a written runbook so the second person pays minutes, not days. Total realistic path to production: 1–2 weeks for a small team with existing queue and storage infrastructure; 3–5 weeks if you must build the container image, sandboxing posture, and backfill pipeline from scratch.
Verdict for planning. Adopt. It is the right-sized abstraction for Laravel teams that need PDF-to-image without owning rendering logic, its risk profile is native-and-operational rather than library-quality, and the MIT license with active Spatie stewardship clears procurement. The gating investment is not the package — it's the hardened runtime image, the isolation boundary for untrusted uploads, and the render-once caching layer that keeps unit economics sane at scale.
How can I help you explore Laravel packages today?