Browsershot alternatives in 2026: Chrome-quality PDFs without Chrome on your PHP server
The best Browsershot alternative keeps what you chose Browsershot for (real Chrome rendering of your Blade templates) and changes what hurts (a Node and Chrome installation riding along on every PHP host). The candidates: a managed API like Transformy, which moves the browser off your infrastructure entirely; Gotenberg, which moves it into its own container; or a small Node sidecar running Puppeteer properly.
Browsershot itself is excellent software with an excellent pedigree: Spatie's fluent wrapper around Puppeteer. Nobody leaves it because the output is wrong; the output is Chrome's. Teams leave because of everything the output requires.
$ php artisan invoice:pdf 1042
Spatie\Browsershot\Exceptions\CouldNotTakeBrowsershot
The command "node …/bin/browser.cjs …" failed.
Error: Could not find Chrome (ver. 127.0.6533.88).
This can occur if either
1. you did not perform an installation before
running the script, or
2. your cache path is incorrectly configured
deploys since last Chrome fix ··· 3
$
KEY TAKEAWAYS
- The pain is the deployment, not the package. Node plus Chrome on every rendering host is an infrastructure decision that came bundled with a composer package.
- Most alternatives relocate the browser rather than remove it. A managed API removes it from your stack; Gotenberg and a Node sidecar move it to infrastructure built for it.
- Serverless PHP and Browsershot fight each other. Chrome-in-Lambda packaging (Vapor included) is a recurring maintenance project; an HTTP API sidesteps it completely.
- Same engine, so migration is renaming. Browsershot's fluent options map one-to-one onto API parameters, and the deployment code disappears rather than moves.
- Going back to pure PHP is legitimate. If the documents turned out to be simple, dompdf or mpdf ends the browser question entirely.
Why Laravel teams look for Browsershot alternatives
The dependency chain is longer than the code. composer require spatie/browsershot is step one of five: Node on the server, Puppeteer installed, Chrome downloaded (and re-downloaded when Puppeteer's cache moves), and paths configured per environment. Local works; staging differs; the Docker image grows by hundreds of megabytes to ship a browser next to PHP-FPM.
Deploys break rendering. The signature failure is Could not find Chrome after a deployment, because the Puppeteer cache did not survive the release, the Chrome version drifted from the Puppeteer version, or the new host never ran the install step. It is fixable every time, and it is also the kind of fix that recurs quarterly.
Serverless makes it a project. On Laravel Vapor and friends, Chrome must be squeezed into layers with size limits and cold-start costs. The community layers work until a runtime or Chrome upgrade moves something; then they are your project again.
Browsers are heavy tenants, wherever they live. Even when the install is stable, PHP hosts now carry Chrome's memory spikes and zombie processes, and scaling PDF traffic means scaling web hosts that mostly do other work.
None of this argues Browsershot is bad software; it argues that a browser is a piece of infrastructure, and infrastructure bolted onto application hosts eventually asks to be moved.
Tip
If Browsershot runs happily on a host you control and deploys have been boring for a year, keep it. The alternatives below are for teams whose deploy checklist has a "verify Chrome" step written in someone's scar tissue.
Where should the browser live?
Nowhere in your stack: your PHP makes an HTTPS call; the browser is the vendor's fleet
Transformymanaged APIIn its own container, in your cluster, off the PHP hosts
Gotenbergin DockerIn its own Node service you write: maximum control, most moving parts
Puppeteer sidecarinternal HTTP endpointNowhere at all, if the documents are honestly simple enough for CSS 2.1-era rendering
dompdf / mpdfpure PHPThe shortlist at a glance
| TOOL | TYPE | ENGINE | NODE/CHROME ON YOUR HOSTS | PRICING | BEST FOR |
|---|---|---|---|---|---|
| Transformy | Managed API | Headless Chrome | No | Free 100 docs/mo, then $99/mo for 10,000 | Same Chrome output, zero browser infrastructure |
| Gotenberg | Self-hosted service | Chromium + LibreOffice | No (Docker in your cluster) | Free (your infra) | Keeping rendering in-house, off the PHP hosts |
| Node sidecar (Puppeteer/Playwright) | Internal service you build | Headless Chrome | Yes, on the sidecar only | Free (your infra) | Teams wanting full control with proper isolation |
| dompdf / mpdf | PHP libraries | Own engines | No | Free | Documents simple enough to skip the browser |
| Headless Chrome CLI | Command-line tool | Headless Chrome | Yes | Free | Artisan commands and cron jobs, not app features |
The managed option: Transformy
Transformy is the shortest path from Browsershot pain to Browsershot output: the same headless Chromium rendering, reached by one authenticated POST from plain PHP. The entire deployment surface (Node, Puppeteer, Chrome binaries, cache paths, Vapor layers) is deleted rather than relocated, which is why the migration diff below is mostly red lines.
use Illuminate\Support\Facades\Http;
$pdf = Http::withToken(config('services.transformy.key'))
->post('https://api.transformy.io/v1/pdf/chrome', [
'html' => view('invoices.show', ['invoice' => $invoice])->render(),
'page_size' => 'a4',
'margin' => '15mm',
'footer' => ['html' => '<span style="font-size:10px">Page {{page}} of {{pages}}</span>'],
])->body();
JavaScript runs and settles before capture (charts and Livewire-rendered markup included), and the production plumbing that Browsershot setups hand-roll comes built in: async jobs with signed webhooks for batch runs, delivery straight to your own S3 bucket, idempotency keys so queue retries cannot double-render, and a free watermarked test mode. Pricing starts free (100 documents a month, no credit card); the Pro plan is $99/month for 10,000 documents, then $0.01 per additional document. The honest trade-offs: rendering leaves your network boundary, which some compliance postures forbid; each render is a network round-trip, so batch pipelines belong on the async mode; and at very large steady volume, self-hosting is cheaper on paper if you have the team for it.
The self-hosted routes
Gotenberg
Gotenberg is the "keep it in-house, get it off the PHP hosts" move: Chromium (and LibreOffice) in a Docker container behind an HTTP API. Your Laravel code calls it exactly the way it would call a managed API, deploys stop touching browsers, and the rendering workload scales independently of your web tier. You still own the container's memory ceiling, upgrade cadence, and scaling; our Gotenberg alternatives page walks through what that ownership costs when it grows.
A Node sidecar running Puppeteer or Playwright
Browsershot already shells out to Puppeteer; the sidecar pattern promotes that relationship to an honest architecture. A small internal Node service exposes one endpoint, runs Puppeteer or Playwright properly (browser pool, recycling, concurrency limits), and your PHP calls it over HTTP. Compared with Browsershot you gain isolation and control; compared with everything else you carry the most moving parts, because now you maintain a service and the browser ops inside it. Our Puppeteer alternatives page covers exactly where that road gets steep.
Back to pure PHP: dompdf or mpdf
The retreat that is sometimes a victory: if the documents that pushed you to Browsershot turned out to be tables and text (invoices, receipts, simple statements), a pure-PHP library ends the browser question. No Node, no Chrome, no sidecar; also no flexbox, no grid, no JavaScript. Whether that ceiling fits your templates is the whole decision, and our dompdf alternatives page maps it from the other direction.
Headless Chrome from the command line
For artisan commands and scheduled jobs that render a report and email it, Chrome's own CLI (chrome --headless --print-to-pdf=… URL) may be all the machinery required, if Chrome is already on that host. It shares Browsershot's deployment requirement without the Node layer, which makes it a niche fit here: right for a cron box you control, wrong as an application dependency.
Migrating: mapping Browsershot's fluent API to Transformy
Same engine underneath, so the options translate directly and the infrastructure code deletes:
- Browsershot::html($html) / Browsershot::url($url)
+ "html" / "url"
- ->format('a4')->landscape()
+ "page_size": "a4", "orientation": "landscape"
- ->margins(15, 15, 15, 15)
+ "margin": "15mm"
- ->showBackground()
+ "print_background": true # already the default
- ->headerHtml(...) / ->footerHtml(...)
+ "header"/"footer" with {{page}}, {{pages}} tokens
- ->waitUntilNetworkIdle()
# usually unnecessary; JavaScript settles before capture
- ->setChromePath(...) ->setNodeBinary(...) + install steps
- npm ci && npx puppeteer browsers install chrome # per deploy
+ gone entirely
- ->save($path)
+ response body to Storage::put, or "output": "storage" straight to your bucket
Budget one test pass over real documents, as with any renderer change, but expect anticlimax: the engine is the same Chrome, so output differences are usually zero. The visible change is the deploy pipeline, which stops caring about browsers.
Frequently asked questions
Is Browsershot still maintained?+
Yes, actively, by Spatie, with the quality that name implies. This page exists because of what Browsershot requires from your infrastructure, not because of the package itself.
Why does Browsershot say "Could not find Chrome" in production?+
Usually one of: the Puppeteer cache directory did not survive your deployment, the Chrome download step never ran on that host, or Puppeteer and Chrome versions drifted apart. The durable fixes are pinning and re-running installs per deploy, or moving rendering somewhere browsers are not your deploy's problem (an API, Gotenberg, or a sidecar).
Does Browsershot work on Laravel Vapor?+
It can, with community Chrome layers and size-limit care, and it remains one of the most fragile parts of a Vapor deployment. An HTTP rendering API is the usual escape: nothing to package, nothing to fit in a layer.
What is the difference between Browsershot and a managed API like Transformy?+
Same rendering engine, different owner of the browser. Browsershot runs Chrome on your hosts via Node; a managed API runs it on the vendor's fleet behind one HTTPS call. Options map one-to-one; the deployment chapter of your runbook is what disappears.
Should I go back to dompdf instead?+
If your documents are genuinely simple (no flexbox, no JavaScript, table-shaped layouts), honestly, maybe: it deletes the browser question entirely. If you moved to Browsershot because dompdf's rendering was not enough, that reason still stands, and the answer is relocating the browser, not retreating from it.
The bottom line
Decide where the browser should live and the list picks itself. Nowhere in your stack: Transformy gives you Browsershot's output without Browsershot's install chapter, and the free tier (100 documents a month, no credit card) is enough to run your real Blade templates through the mapping table above. In your cluster but off the PHP hosts: Gotenberg. Under your full control: a Node sidecar running Puppeteer properly. Out of the picture entirely: dompdf or mpdf, if the documents allow it.
Browsershot made Chrome rendering feel like a composer package. The alternatives above are for the day you notice the browser never really fit inside one.