Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

Use a real browser renderer, not an HTML parser. In Laravel, Spatie Browsershot drives headless Google Chrome through Puppeteer and can render either a URL or an HTML string, then save the result as a PNG. PNG is its default image format, so a path ending in .png is usually all you need. For a Laravel-style facade, Spatie Laravel Screenshot wraps this workflow and also offers a Cloudflare Browser Rendering driver.

This guide shows complete local-rendering examples, explains timing and sizing controls, covers deployment and failure modes, and then shows a hosted option when installing Node.js and Chrome is inconvenient.

Choose the rendering route

Route Input Where rendering runs Best fit
Browsershot directly URL or arbitrary HTML Your server’s Puppeteer-controlled headless Chrome Maximum low-level control when browser dependencies can be installed
Laravel Screenshot Laravel facade API Browsershot by default; Cloudflare Browser Rendering when configured A Laravel-oriented workflow with documented defaults and queueing examples
Cloudflare driver for Laravel Screenshot Laravel Screenshot requests Cloudflare Browser Rendering Deployments that should avoid a local Node.js installation or Chrome binary

Official documentation does not establish a universal speed, price, or throughput winner. Compare the options by dependency management, whether you can provide HTML directly or must expose a URL, viewport and readiness controls, and whether the browser runs locally or in a hosted service.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install and prepare a local Browsershot renderer

Browsershot’s path is PHP calling a Node/Puppeteer process that controls headless Chrome. Install the package versions appropriate for your Laravel application, then install the Node and browser dependencies required by the current Browsershot documentation. The exact commands and supported versions change, so use the package’s current installation guide rather than pinning an old command from a blog post.

  • Confirm that PHP can start the Node process under the same user as your queue worker or web server.
  • Ensure that Chrome or the supported Chromium binary is present and executable in production.
  • Give the process enough shared memory, CPU, and temporary storage for the pages you render.
  • If screenshots run in a queue, install dependencies in the worker image as well as the web image.

For a Laravel facade and configurable drivers, install Spatie Laravel Screenshot and configure its default Browsershot driver as described by its current setup documentation. Its documented defaults are a 1280×800 viewport, a 2× device scale factor, PNG output, and waiting for network idle; treat those as package defaults, not immutable output guarantees.

Convert an HTML string to PNG with Browsershot

Use Browsershot::html() when the markup is already available in your application. The target filename determines the output extension in this basic example.

<?php

namespace AppHttpControllers;

use IlluminateHttpResponse;
use SpatieBrowsershotBrowsershot;

class CardController extends Controller
{
    public function png(): Response
    {
        $html = view('cards.share', [
            'title' => 'Quarterly report',
            'value' => '$42,800',
        ])->render();

        $path = storage_path('app/public/cards/report.png');

        Browsershot::html($html)
            ->windowSize(1200, 630)
            ->save($path);

        return response()->file($path, [
            'Content-Type' => 'image/png',
        ]);
    }
}

Create the destination directory before saving, or ensure your storage setup creates it. If you want a persistent public URL, store the file on the disk configured for your application and return a signed or authenticated download route instead of exposing a private filesystem path.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Render a URL instead

When the page is already served and its CSS, fonts, and JavaScript resolve through normal web URLs, use Browsershot::url():

$path = storage_path('app/public/pages/home.png');

Browsershot::url('https://example.com')
    ->windowSize(1440, 900)
    ->save($path);

A URL capture follows the page’s normal network behavior. An HTML-string capture is useful for private, unsaved views, but external assets must still be reachable by the browser unless you inline them or provide accessible local URLs.

Control dimensions, content, and readiness

Viewport and full-page output

Set the viewport deliberately so responsive breakpoints produce the intended design. Use the package’s full-page option when the PNG should include content below the initial viewport. A full-page image can become extremely tall; for social cards, invoices, and thumbnails, a fixed width and height is usually safer.

Retina scale and backgrounds

A device scale factor changes pixel density without changing CSS layout dimensions. Laravel Screenshot documents a 2× default, but configure it explicitly when another consumer requires a specific pixel size. Preserve CSS backgrounds when they are part of the design, and set a transparent background only when your compositing workflow needs alpha transparency.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Wait for the actual page state

Network-idle waiting is convenient for pages whose requests finish, but analytics, websockets, polling, and advertising can keep a page busy indefinitely. Prefer a short, bounded delay for predictable animations or wait for a selector that proves the data is present. Browsershot’s current documentation also describes waiting for selectors or functions. Avoid an unbounded wait: always pair readiness logic with an operational timeout.

Browsershot::html($html)
    ->windowSize(1200, 630)
    ->waitForSelector('.chart-ready')
    ->save(storage_path('app/public/chart.png'));

Fonts, images, and JavaScript

  • Use absolute, reachable URLs for remote assets, or inline critical CSS and images for an HTML-string render.
  • Wait until client-side charts and lazy images have finished before capturing.
  • Disable transitions or add a deterministic delay when an animation can leave an intermediate frame.
  • Keep secrets out of HTML and query strings; use controlled headers or cookies only when the target page requires authentication.

Use Laravel Screenshot for a facade workflow

Laravel Screenshot provides a Laravel-focused API around screenshot generation. Its overview documents screenshot options, queueing examples, and a Browsershot driver by default. A typical controller keeps rendering concerns behind the facade while you configure dimensions and output for each job. Check the package’s current namespace and installation instructions for your installed release before copying imports, because package APIs can evolve.

The package also documents a Cloudflare Browser Rendering driver. That route sends rendering to Cloudflare and avoids requiring local Node.js or a Chrome binary, which can simplify a restricted container or serverless deployment. It is not established as universally cheaper or faster; evaluate it with your own workload, region, limits, and credentials.

Make captures reliable in production

Queue expensive work

Browser startup and page rendering are poor fits for a request that must return instantly. Dispatch a job, write the PNG to durable storage, and notify the caller when it is ready. Set a job timeout longer than the browser timeout, and clean up temporary files after upload.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Control concurrency

Each concurrent Chrome page consumes memory. Start with a small worker count, observe failures, and increase it only after measuring your host. Reuse a browser process only when your chosen integration safely isolates pages and clears cookies and local storage between jobs.

Make output deterministic

  • Pin the browser and package versions in your deployment image.
  • Use a fixed timezone, locale, and viewport when text wrapping or dates matter.
  • Wait for a semantic readiness marker rather than guessing with a long delay.
  • Cache identical inputs using a content hash and invalidate the cache when templates or assets change.

Troubleshooting common failures

“Chrome/Chromium not found” or process launch errors

Cause: the worker image lacks the browser, the executable path is wrong, or the runtime user cannot execute it. Fix: install the supported browser dependencies in the same image that runs the job, configure the documented executable path, and test as the queue user.

Node or Puppeteer cannot be started

Cause: Node is absent from the production image, permissions block the binary, or the PHP process has a different PATH. Fix: invoke the configured absolute paths, grant execute permission, and log the environment seen by the worker.

Blank or partially rendered PNG

Cause: capture occurred before JavaScript, fonts, lazy images, or data requests completed. Fix: wait for a selector or function tied to the finished state, verify asset URLs from the server, and disable animations.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Timeout while waiting for network idle

Cause: persistent polling, websockets, or third-party requests never become idle. Fix: replace network-idle waiting with a selector or bounded delay, and block nonessential requests where your integration supports it.

Different layout in production

Cause: viewport, device scale, fonts, timezone, or user-agent differs between environments. Fix: set these values explicitly and package the same fonts and browser revision in every environment.

Private assets return 401 or 403

Cause: the headless browser has no session or authorization header. Fix: provide narrowly scoped cookies or headers through the documented Browsershot options, or render assets through a controlled internal endpoint.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is a hosted screenshot API and MCP server. One request can render a URL as PNG, JPEG, WebP, or PDF, without you packaging Chrome. Before capture it accepts cookie/consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Its API supports full-page and CSS-selector captures, dark mode, device presets or custom viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Use the same endpoint from Laravel (or any server-side PHP code):

use IlluminateSupportFacadesHttp;

$response = Http::timeout(90)->get('https://api.screenshotneo.com/v1/shot', [
    'access_key' => config('services.screenshotneo.key'),
    'url' => 'https://example.com',
]);

$response->throw();
Storage::disk('public')->put('screenshots/example.webp', $response->body());

See the ScreenshotNeo documentation for authentication and options. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Equivalent requests in cURL, Python, and Node.js

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

Frequently Asked Questions

Can I convert a Blade view without publishing a route?

Yes. Render the view with view(...)->render(), pass the resulting string to Browsershot::html(), and save the PNG to storage.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is PNG always the smallest output?

No. PNG preserves sharp text and transparency but can be larger than JPEG or WebP; choose the format required by the consuming system.

Should I use a URL or an HTML string?

Use a URL when the page’s normal asset and authentication context is already available. Use an HTML string for unsaved or private markup, while making its external assets reachable.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.