How to Convert HTML to PNG in Laravel
Convert an HTML string or URL to PNG in Laravel with Spatie Browsershot, configure capture options, and handle deployment and rendering issues.
To convert HTML to PNG in Laravel, render the HTML in a headless browser and save the result to a .png path. Spatie Browsershot accepts either an HTML string or a URL and controls headless Chrome through Puppeteer. PNG is its documented default image format. For a Laravel-style facade and configurable drivers, Spatie Laravel Screenshot wraps screenshot generation and supports Browsershot and Cloudflare Browser Rendering. See Browsershot’s image documentation.
Choose how to render the page
| Approach | Use it when | Consider |
|---|---|---|
| Browsershot directly | You want direct control and can install and run the browser dependencies in your Laravel environment. | It renders through Puppeteer and headless Chrome. Your deployment must have the needed Node.js, Chrome, and package setup. |
| Laravel Screenshot | You prefer a Laravel facade and package defaults for dimensions, scale, format, and readiness. | Its documented defaults are 1280×800, 2× device scale, PNG, and waiting for network idle. Configure them to fit the page. |
| Laravel Screenshot with Cloudflare driver | You want rendering through Cloudflare Browser Rendering and do not want to require a local Node.js installation or Chrome binary. | It relies on a hosted browser-rendering service. Check current service requirements and pricing for your workload. |
These sources establish capabilities and setup differences, not a general ranking for speed or cost. Choose based on runtime dependencies, whether you start with raw HTML or a served URL, the capture controls you need, and where rendering should run. Laravel Screenshot overview · Cloudflare driver setup.
Install Browsershot and capture an HTML string
Install the package and its documented browser tooling for your environment, then pass the HTML and a PNG output path. The exact system setup depends on the host; use the package’s current installation instructions when configuring Node.js and Chrome.
composer require spatie/browsershot
<?php
use Spatie\Browsershot\Browsershot;
$html = '<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body { font: 16px sans-serif; padding: 32px; }
h1 { color: #e3342f; }
</style>
</head>
<body>
<h1>Invoice preview</h1>
<p>Rendered from Laravel.</p>
</body>
</html>';
$output = storage_path('app/invoice-preview.png');
Browsershot::html($html)->save($output);
The example writes to Laravel’s local storage path. Ensure the PHP process can write there, and create the directory if your chosen output directory does not already exist. Browsershot documents html(...)->save(...) for HTML input and image output. Creating images with Browsershot.
Capture a URL
If the content is already served by your Laravel app or another site, pass the URL instead. The URL must be reachable from the process running the browser; a private hostname available only from a developer’s laptop will not work from an isolated server or container.
<?php
use Spatie\Browsershot\Browsershot;
$url = route('reports.show', ['report' => 42]);
$output = storage_path('app/report.png');
Browsershot::url($url)->save($output);
For authenticated pages, make sure the browser can access the page using the required session or request configuration. Avoid putting secrets in publicly accessible URLs or logs. For more controls, see Browsershot’s options.
Set the capture size and readiness deliberately
A screenshot is a rendered browser viewport, so output depends on viewport dimensions, device scale, page styling, and when capture begins. Laravel Screenshot documents defaults of 1280×800, 2× device scale, PNG, and waiting for network idle. Those are package defaults, not guarantees after configuration. Browsershot documents controls for image sizing, full-page capture, device scale, background behavior, delays, and waiting for selectors or functions.
- Viewport: choose dimensions that match the intended layout; responsive breakpoints can change what appears.
- Full page: use a full-page option when you need the complete document rather than only the viewport.
- Device scale: increase scale for denser output while accounting for larger image dimensions and memory needs.
- Background: configure background handling if transparent output or exact page colors matter.
- Readiness: wait for a known selector or page condition when asynchronous content is required. A fixed delay is simple but can waste time or still be too short.
- Network idle: useful for pages that settle, but persistent polling or analytics traffic can prevent a quiet network state. In that case use a page-specific selector or condition.
Consult the Browsershot image options and Laravel Screenshot configuration examples for the API supported by the installed package version.
Use the Laravel Screenshot facade
Laravel Screenshot provides a Laravel-oriented API and configurable driver. Its documented overview includes screenshot dimensions and queueing examples; the selected driver still determines runtime dependencies. Follow its current installation guide to install the package and select a driver.
// Illustrative facade usage; configure and install the package and driver
// according to the current Laravel Screenshot installation documentation.
use Spatie\LaravelScreenshot\Facades\Screenshot;
Screenshot::url(route('reports.show', ['report' => 42]))
->save(storage_path('app/report.png'));
Verify the facade method and option names against the installed release’s documentation before using this snippet: package APIs can change. Start with the official Laravel Screenshot documentation and its Cloudflare driver guide. The direct Browsershot snippets above use the documented Browsershot::html() and Browsershot::url() methods.
When rendering runs in a queue or cloud deployment
Browser startup and page rendering take longer than ordinary string processing. For user-facing requests, consider dispatching capture work to a queue and returning a job identifier or status rather than keeping an HTTP request open. Laravel Screenshot documents queueing examples. Make output paths unique per job and clean up generated files according to your retention needs.
For deployments where installing Node.js and Chrome locally is undesirable, Laravel Screenshot’s Cloudflare driver calls Cloudflare Browser Rendering and avoids the local Node.js or Chrome binary requirement. Compare the current setup, limits, and pricing for the relevant services; the cited package docs do not establish that hosted rendering is faster or cheaper. In either setup, account for browser process memory and concurrency in your own workload planning rather than assuming a fixed throughput.
cURL, Python, and Node.js alternatives
These examples call a screenshot API instead of running Chrome inside the Laravel host. Replace the target URL as needed and keep API keys out of source control. The ScreenshotNeo endpoint accepts a URL and returns a screenshot image; its API configuration is documented at ScreenshotNeo docs.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
In production, check the HTTP status and response headers before treating the response body as an image, and handle network timeouts and API errors. The compact examples show the request shape; extend them with your application’s error handling and secure key storage.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request can return PNG, JPEG, WebP, or PDF. Use the API from Laravel when you want to avoid installing and maintaining browser tooling on the application host:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers identify the page verdict and whether the request was billed. The MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000. See the API documentation, then sign up free for 1,000 screenshots a month with no card.
Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| Browser executable or process cannot be found | Chrome or the configured browser dependencies are missing or not available to the PHP process. | Follow current Browsershot installation requirements for the deployment image and verify the runtime user can execute the browser. Consider the documented Cloudflare driver if local Node.js and Chrome are not suitable. |
| Permission denied when saving | The PHP worker cannot write to the target directory. | Use an application-writable storage directory, create it before capture, and check ownership and permissions for the worker user. |
| PNG is blank or missing dynamic content | Capture started before scripts, fonts, images, or asynchronous page data completed. | Wait for a specific selector or page condition, or use an appropriate delay. Confirm required assets are reachable from the renderer. |
| Capture hangs while waiting for network idle | The page keeps network connections active, for example through polling. | Choose a selector or application-specific readiness condition instead of waiting for all network activity to stop. |
| Layout differs from a browser screenshot | Viewport, device scale, fonts, browser environment, or responsive breakpoints differ. | Set the intended viewport and scale, ensure fonts and assets load, and compare with the same browser conditions. |
| URL works locally but not in production | The server-side browser cannot resolve or reach the same host, or the page requires authentication. | Check DNS, network access, TLS, firewall rules, and the authentication context from the rendering environment. |
| Output is not a PNG | The target extension or explicit image configuration does not match the desired format. | Use a .png target path and check the documented format options for the installed version. |
Performance, reliability, and cost
- Performance: Browser launch, JavaScript execution, asset loading, full-page capture, and high device scale can add work. Reuse a queue for bursty requests and cap concurrency based on measured resource use in your deployment.
- Reliability: Pages depend on external assets and scripts that can be slow or unavailable. Use explicit readiness conditions, bounded timeouts, and retry only failures that are plausibly transient. Keep retries limited to avoid duplicate work.
- Cost: Local rendering has infrastructure and maintenance costs, but the cited documentation supplies no universal cost comparison. Hosted rendering has service-specific pricing and limits to check. Measure your own workload before choosing.
- Output size: Larger viewports, full-page capture, and high device scale can increase image dimensions and storage or transfer needs. Select only the resolution your downstream use requires.
FAQ
Can I convert an HTML string without first saving it as a file?
Yes. Browsershot documents passing HTML directly with Browsershot::html($html).
Does Browsershot save PNG by default?
PNG is the documented default image type. A .png output path makes the intended format clear.
Should I wait for network idle on every page?
No. It is one readiness strategy. Pages with persistent network activity may never become idle; wait for a page-specific selector or condition instead.
Do I need Chrome installed when using the Cloudflare driver?
The Laravel Screenshot Cloudflare driver documentation says it does not require a local Node.js installation or Chrome binary. Check its current setup guide for the hosted service configuration.
Can this capture a full page rather than the visible viewport?
Browsershot documents full-page capture controls. Configure that explicitly and account for the larger output.


