Convert HTML to PDF in Laravel with Browsershot
Render trusted HTML or a Laravel Blade view as a PDF with Browsershot. Set up Node and Chrome, control page output, and troubleshoot common rendering issues.
Use Spatie Browsershot to render an HTML string or URL in a browser and save the result as a PDF. For a string, the core call is Browsershot::html($html)->savePdf($path). Browsershot v4 requires Node.js 22.0 or higher and Puppeteer 23.0 or higher, plus an available Chrome or Chromium browser. Validate any HTML and URL passed to the renderer: it launches a browser process that can access resources.
This guide covers direct Browsershot use. If you want a Laravel-oriented facade for Blade views and HTML, Spatie Laravel PDF v2 provides one with multiple rendering drivers. The two package generations and their runtime requirements are distinct; confirm the version and driver you install.
1. Install and prepare the runtime
Install Browsershot v4’s documented requirements: Node.js 22.0+, Puppeteer 23.0+, and Chrome or Chromium. Follow the package setup instructions for installing Puppeteer and a browser. If automatic browser discovery fails, configure the executable paths using Browsershot’s documented settings.
Browsershot is a PHP interface to a browser-based renderer. PHP package installation alone does not provide Node, Puppeteer, or Chrome. In deployment, make sure the PHP process that runs the job can execute Node, read the browser binary, and access any required assets.
2. Render an HTML string to PDF
Install the Browsershot PHP package following its versioned installation instructions, then pass trusted HTML to html() and save to a location managed by Laravel:
<?php
use Spatie\Browsershot\Browsershot;
$html = '<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<style>
body { font-family: sans-serif; margin: 0; }
h1 { color: #243b53; }
</style>
</head>
<body>
<h1>Invoice</h1>
<p>Rendered from HTML with Browsershot.</p>
</body>
</html>';
$path = storage_path('app/invoice.pdf');
Browsershot::html($html)
->format('A4')
->savePdf($path);
The HTML and PDF methods are documented in Browsershot v4’s PDF creation guide. Use an application-controlled output path; do not let a request parameter choose an arbitrary filesystem destination.
Return a generated file from a Laravel route
For an HTTP download, generate the PDF into a controlled temporary or application storage path, then return it through Laravel’s response helpers. For repeated or expensive documents, prefer a queued job and store the result rather than rendering synchronously on every request.
use Illuminate\Support\Facades\Storage;
use Spatie\Browsershot\Browsershot;
Route::get('/reports/{report}/pdf', function (Report $report) {
// Authorize access before rendering or returning a private report.
abort_unless(auth()->user()->can('view', $report), 403);
$html = view('reports.pdf', ['report' => $report])->render();
$relativePath = 'generated/reports/report-'.$report->id.'.pdf';
$absolutePath = Storage::disk('local')->path($relativePath);
Browsershot::html($html)
->format('A4')
->savePdf($absolutePath);
return Storage::disk('local')->download($relativePath, 'report.pdf');
});
Adapt storage and response handling to your disk configuration. A persistent filename can be overwritten by concurrent requests, so use a unique name or serialize generation when multiple requests may target the same document.
3. Render a URL or a Blade view
When the target is an application page, Browsershot can render a URL and save it as a PDF. Only use a URL that your application has constructed or explicitly allowed; do not pass a caller-supplied URL directly to a browser renderer.
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/reports/monthly')
->format('A4')
->savePdf(storage_path('app/monthly-report.pdf'));
For authenticated application pages, consider rendering the Blade view to HTML in the application and passing that string to Browsershot. This avoids exposing a public route solely for rendering, but ensure the generated markup and referenced assets are still safe and accessible to the browser process.
Using Spatie Laravel PDF for a Blade view
Laravel PDF v2 provides a Laravel facade for rendering a Blade view or an HTML string. Its Browsershot driver uses Browsershot and inherits its runtime needs.
use Spatie\LaravelPdf\Facades\Pdf;
Pdf::view('pdf.invoice', ['invoice' => $invoice])
->save(storage_path('app/invoice.pdf'));
Check the Laravel PDF documentation for installation and setup for the exact package generation and driver you configure. A Blade view does not by itself guarantee browser rendering: the selected driver determines which rendering engine and capabilities are used.
4. Configure paper size, margins, and page output
Browsershot documents PDF output controls including format or paper size, margins, headers and footers, and page selection. Configure only the options the document needs; the browser’s print layout and CSS also affect pagination.
Browsershot::html($html)
->format('A4')
->marginTop(12)
->marginRight(12)
->marginBottom(16)
->marginLeft(12)
->savePdf(storage_path('app/report.pdf'));
Check the versioned PDF options reference for the exact method names and units supported by your installed Browsershot release. Other documented controls cover headers and footers and selecting pages. Validate page selection against the document’s actual page count, especially when content length varies.
- Paper format: choose a standard format such as A4, or use the relevant custom dimensions supported by the installed version.
- Margins: leave room for printer-safe content and avoid placing important text at page edges.
- Headers and footers: use these for repeating metadata such as page numbers or a document title; verify the template in the PDF because browser print templates have their own constraints.
- Page selection: useful for extracting selected pages, but it is not a substitute for controlling source content.
- Print CSS: use
@media printand page-break rules to shape layout; verify font loading and long tables with realistic content.
5. Validate HTML, URLs, and assets
Spatie explicitly assigns callers responsibility for validating URLs and HTML passed to Browsershot. Treat this as a core security boundary. HTML rendering runs a browser with access to network and local resources, so untrusted input can have effects beyond producing an unexpected-looking PDF.
- Build markup from trusted templates and escaped data. Do not concatenate arbitrary user-provided HTML into a document.
- Do not render arbitrary submitted URLs. If URL rendering is required, use a strict host allowlist and reject loopback, private, link-local, and other internal destinations.
- Keep output paths application-controlled and prevent path traversal.
- Restrict access to generated PDFs with authorization checks; PDFs often contain sensitive data.
- Use least-privilege process and filesystem permissions. Review any configuration that relaxes browser security, and only consider it for trusted content.
- For local CSS, images, and fonts, confirm that the browser process can access them. Relative paths may resolve differently from the PHP request or web server.
6. Choose the Laravel PDF driver that fits
Laravel PDF v2 documents several drivers. Choose based on JavaScript needs, CSS support, and the dependencies your deployment can operate. The documentation is a feature overview, not a performance benchmark.
| Driver or approach | Useful when | Runtime considerations |
|---|---|---|
| Browsershot | You need browser rendering and JavaScript execution. | Requires the Browsershot package and its Node, Puppeteer, and browser setup. |
| Gotenberg | You prefer a separate rendering service/container. | Requires operating or accessing the service; review its setup and network controls. |
| Cloudflare | You want hosted browser rendering through Browser Run. | Laravel PDF documents that this driver does not require local Node.js or Chrome, but it requires Cloudflare credentials and service access. |
| Chrome | You want a browser-based driver and can provide the relevant browser runtime. | Review the specific driver setup and binary requirements. |
| WeasyPrint | Your layouts benefit from CSS Paged Media. | Install and operate its runtime dependencies as documented. |
| DOMPDF | You have simpler layouts and do not need JavaScript execution. | Laravel PDF documents that DOMPDF does not execute JavaScript. |
See the Laravel PDF v2 driver overview and installation and setup guide. Do not assume all drivers have identical CSS support, JavaScript behavior, or infrastructure needs.
7. Performance, reliability, and cost
A browser render starts or communicates with a browser process and loads the document’s dependencies. Keep templates bounded, avoid unnecessary remote assets, and avoid rendering the same unchanged document repeatedly when storing or caching a generated artifact is appropriate. Queue long-running work so a slow page render does not consume a web request worker.
There is no documented benchmark in the cited package material to support a speed ranking. Measure representative documents in your own deployment, including cold starts, image-heavy pages, and peak concurrency. Set request and job timeouts with enough headroom for the real document, and ensure failed jobs can be retried without creating inconsistent output.
Self-hosted Browsershot has infrastructure costs for PHP, Node, Puppeteer, browser binaries, and the compute used to render documents. Hosted drivers shift some runtime operations to a service and may involve service charges or credentials. Check the service and package terms applicable to your deployment; the cited documentation does not establish a universal cost comparison.
8. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Node or npm executable not found | Node is missing, incompatible, or unavailable in the PHP worker’s PATH. | Install the required Node version and configure the documented executable path for the process that actually runs PHP jobs. |
| Puppeteer or Chrome cannot be found | The browser was not installed, automatic discovery failed, or the configured path is wrong. | Follow Browsershot’s setup instructions, check browser permissions, and configure the executable path explicitly if necessary. |
| PDF is blank or content is missing | The page did not finish loading, a URL was inaccessible, or the markup references resources the browser cannot fetch. | Render trusted self-contained HTML where practical, ensure asset URLs are reachable from the browser process, and wait for required page content before printing. |
| Styles, fonts, or images are absent | Relative asset paths resolve differently, local resources are blocked, or remote resources fail. | Use valid absolute or correctly based paths, verify network and file access under the worker identity, and inspect the browser restrictions before changing security settings. |
| JavaScript-generated content is missing | The selected driver does not execute JavaScript, or rendering starts before the app has populated the page. | Use a browser-based driver such as Browsershot and wait for the content your template needs. DOMPDF does not execute JavaScript. |
| Layout differs from the browser view | PDF print rules, paper dimensions, margins, or browser font availability differ from screen rendering. | Add print-specific CSS, set the paper format and margins, install or load required fonts, and inspect page breaks with long and short data. |
| Works locally but fails in a queue/container | The runtime, browser binary, permissions, environment variables, or asset access differ in that worker. | Install and configure dependencies in the same image/environment as the worker; check executable permissions and paths as that process user. |
| Requests hang or exceed job timeout | Remote assets or pages are slow, the document is large, or browser startup is constrained. | Reduce external dependencies, use a queue, set an appropriate timeout, and investigate a representative failing document rather than retrying without limits. |
Or skip the browser setup
If your goal is a PDF of a website page, ScreenshotNeo returns a webpage screenshot as PNG, JPEG, or WebP with one GET request. It returns an image, not a PDF, so it is suitable when a captured page image is acceptable; it does not replace a multi-page PDF renderer for invoices or long reports. Its cookie/consent banner handling, pop-up and chat-widget removal can be turned off, and response headers identify page verdict and billing status.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the ScreenshotNeo API documentation. Cookie banners, popups, and chat widgets are removed before capture; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents use screenshot tools. The Free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up for 1,000 free screenshots a month, no card required.
FAQ
Can Browsershot create a PDF from a Blade view?
Yes. Render the view to an HTML string and pass it to Browsershot::html(), or use Laravel PDF’s view facade with an appropriate driver.
Does every Laravel PDF driver run JavaScript?
No. Browser-based drivers do; the Laravel PDF documentation specifically says DOMPDF does not.
Which Browsershot requirements does this guide assume?
Browsershot v4 documentation: Node.js 22.0 or higher and Puppeteer 23.0 or higher, with Chrome or Chromium available and configured.
Can I render user-submitted HTML safely?
Do not pass arbitrary markup or URLs directly to the renderer. Validate inputs, control resource access, and follow the browser security guidance for your chosen deployment.


