ScreenshotNeo

BlogHow-to

How to Capture a Website Screenshot in PHP with Browsershot

Install Browsershot and its browser runtime, capture a URL in PHP, and control the viewport, output, and page readiness.

By the ScreenshotNeo team4 October 202610 min read

Use spatie/browsershot from PHP to ask Puppeteer-controlled headless Chrome to render a URL and save the result. The shortest working capture is Browsershot::url($url)->save($path). Install the PHP package and its Node.js, Puppeteer, Chrome, and Linux runtime dependencies first; then choose a viewport, capture area, and readiness condition that match the page.

This guide targets Browsershot v4. Its requirements specify Node 22.0 or newer and Puppeteer 23.0 or newer. Check the official requirements and installation guide for your operating system and deployment environment.

1. Install Browsershot and its browser runtime

Install the PHP package with Composer:

composer require spatie/browsershot

Browsershot depends on Puppeteer, which controls headless Chrome. Install Node and Puppeteer and make Chrome available to the same environment that runs your PHP process. For example, Spatie documents this sequence for a Forge-provisioned Ubuntu 24.04 server:

node -v && npm -v
sudo npm install -g puppeteer
npx puppeteer browsers install chrome

Chrome also needs system libraries on Linux. The required packages vary by distribution and version; follow the official requirements page for your server rather than copying a package list from a different operating system. Spatie notes, for example, that Ubuntu 22.04 uses libasound2 in place of Ubuntu 24.04’s libasound2t64.

Confirm that the PHP worker, queue worker, or web server can find Node, npm, and Chrome. A command that works in an interactive shell can still fail under a service account with a different PATH.

2. Capture a website in PHP

Save the following as a PHP file in a project where Composer’s autoloader is available. The destination directory must exist and be writable by the PHP process.

<?php

require __DIR__ . '/vendor/autoload.php';

use Spatie\Browsershot\Browsershot;

$url = 'https://example.com';
$output = __DIR__ . '/screenshot.png';

Browsershot::url($url)
    ->save($output);

if (! is_file($output) || filesize($output) === 0) {
    throw new RuntimeException('Screenshot was not created.');
}

echo "Saved screenshot to {$output}" . PHP_EOL;

PNG is the default screenshot format. Use an absolute path when running from a web request, scheduled job, or queue whose working directory may differ from your project directory.

Capture HTML you already have

For HTML held in your application, pass the markup to html() instead of navigating to a URL:

<?php

require __DIR__ . '/vendor/autoload.php';

use Spatie\Browsershot\Browsershot;

$html = '<!doctype html><html><body><h1>Invoice preview</h1></body></html>';

Browsershot::html($html)
    ->save(__DIR__ . '/preview.png');

Relative asset URLs in supplied HTML need a base URL or another way for the browser to resolve them. When the content is a live webpage, use url().

3. Choose what to capture

Pick the capture scope deliberately. A viewport screenshot shows the visible browser area; a full-page screenshot includes the page’s vertical length; an element screenshot targets a selector; and a clip captures a specific rectangle.

Goal Browsershot method Example
Visible viewport Default behavior windowSize(1280, 800)
Entire page height fullPage() fullPage()->save(...)
One element select($selector) select('.report-card')
Rectangular region clip($x, $y, $width, $height) clip(0, 100, 800, 600)

For example, a full-page capture with a defined desktop viewport is:

Browsershot::url('https://example.com')
    ->windowSize(1280, 900)
    ->fullPage()
    ->save(__DIR__ . '/full-page.png');

To capture a matching element, wait for it to appear before selecting it. The optional selector index chooses a later match; the default is the first matching element.

Browsershot::url('https://example.com/dashboard')
    ->waitForSelector('.report-card')
    ->select('.report-card')
    ->save(__DIR__ . '/report-card.png');

See Spatie’s image guide for the documented sizing, clipping, element, full-page, device scale, and device emulation options.

4. Wait for the page to be ready

A page can return its initial HTML before client-side rendering, lazy-loaded images, or web fonts are ready. Use a readiness signal that reflects the page you are capturing:

  • waitUntilNetworkIdle() waits for 500 ms without network activity. Use it when the page finishes loading its assets and requests eventually settle.
  • waitUntilNetworkIdle(false) is less strict and tolerates two active connections during that interval, which can help with pages that keep a connection open or periodically poll.
  • waitForSelector('.ready') waits for a known element to appear.
  • waitForFunction('...') waits for a JavaScript condition to become true.
  • setDelay($milliseconds) adds a fixed delay. Use it when a known animation or client-side action needs time, not as a universal substitute for a readiness signal.

Example using network idle for a page that loads additional assets:

Browsershot::url('https://example.com')
    ->waitUntilNetworkIdle()
    ->fullPage()
    ->save(__DIR__ . '/ready-page.png');

For an application with a reliable state marker, prefer that marker:

Browsershot::url('https://example.com/app')
    ->waitForSelector('[data-render-state="complete"]')
    ->save(__DIR__ . '/app.png');

Network idle can wait longer than expected on pages with analytics, polling, or streaming requests. In that case, wait for the relevant selector or JavaScript condition instead. Spatie documents these waiting methods in its image usage reference.

5. Set output format, size, and device presentation

Set JPEG and its quality when a smaller lossy image is suitable. PNG is the default and is often a good fit for sharp text or UI captures.

Browsershot::url('https://example.com')
    ->setScreenshotType('jpeg', 90)
    ->windowSize(1280, 800)
    ->save(__DIR__ . '/screenshot.jpg');

Use windowSize($width, $height) to choose the viewport. Device scale factor changes the pixel density of the output; mobile emulation and device presets affect how the page is presented to the browser. If the target layout depends on phone behavior, configure the viewport and mobile/device emulation intentionally rather than only shrinking the desktop window.

A full-page capture can be much taller than its viewport, particularly on long pages. Consider whether a viewport, clipped region, or element capture better matches the downstream use. These choices affect output dimensions and file size.

6. Other useful capture controls

Browsershot offers additional controls for rendering and content selection. Use the method names and signatures in the versioned Spatie documentation for the installed release.

  • Mobile and device appearance: configure mobile emulation, a device preset, viewport, user agent, and device scale factor for the presentation you need.
  • Page styling: add custom CSS or JavaScript when the page needs a controlled visual state before capture.
  • Network content: disable images or block selected URLs/domains, such as known trackers, when those resources are not needed in the image.
  • Page behavior: dismiss dialogs, disable JavaScript, or set a user data directory when your capture workflow requires it.
  • Output delivery: use base64Screenshot() when the application needs screenshot data directly instead of a file path.
  • PDF: save to a path ending in .pdf and configure PDF options such as paper format as needed; see the PDF guide.

Blocking resources changes what the page can render, so do not block scripts, fonts, or images required for the intended result. If a site needs authentication, make sure the browser receives the required session or credentials through an appropriate supported configuration; never place secrets in source control.

7. Run Browsershot in a server environment

Web requests and queue workers often run with a restricted PATH or a different user than an interactive shell. If Browsershot cannot locate a runtime binary, configure its path explicitly. For example:

Browsershot::url('https://example.com')
    ->setNodeBinary('/usr/bin/node')
    ->setNpmBinary('/usr/bin/npm')
    ->setChromePath('/usr/bin/google-chrome')
    ->save(__DIR__ . '/screenshot.png');

Use paths that exist in your own environment. The requirements reference also documents setIncludePath(), setNodeModulePath(), setNodeEnv(), and setBinPath() for environments that need a custom PATH, module location, Node environment, or script path. The server must have the corresponding files and system libraries installed.

Capture jobs consume browser and memory resources. For user-facing applications, consider running captures through a queue with a bounded worker count, an execution timeout, and a writable temporary or destination directory. Clean up temporary files according to your application’s retention needs.

8. cURL, Python, and Node.js alternatives

If your application is not PHP, a browser automation library or screenshot API can perform the same general task. The PHP examples above remain the direct Browsershot approach; these snippets show a one-request alternative through ScreenshotNeo’s screenshot API.

cURL

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com \
  -o shot.webp

Python

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
r.raise_for_status()
with open("shot.webp", "wb") as f:
    f.write(r.content)

Node.js

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

For ScreenshotNeo’s supported capture parameters, response headers, and formats, see the API documentation.

9. Or skip the browser setup

Browsershot keeps the rendering runtime in your PHP environment. If you would rather make an API request, ScreenshotNeo is a website screenshot API and MCP server for developers. Send one GET request with a URL to receive a PNG, JPEG, WebP, or PDF.

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 like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, and response headers identify the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Read the ScreenshotNeo API docs, then sign up for 1,000 free screenshots a month, no card required.

10. Performance, reliability, and cost

  • Rendering time: the browser must start or be available, load the page, and satisfy the chosen readiness condition. Waiting for network idle may take longer on busy pages; selector-based waits can avoid waiting for unrelated requests.
  • Memory and image size: full-page screenshots and high device scale factors produce more pixels and can require more memory and storage than a viewport capture. Choose the smallest capture area and density that meet your needs.
  • Reliability: the result depends on the target site, browser runtime, system libraries, network access, and permissions on the output directory. Use timeouts and bounded queue workers in server applications, and surface capture failures rather than treating an absent file as success.
  • Cost: self-hosted Browsershot has no per-shot ScreenshotNeo charge, but you operate the PHP package, Node/Puppeteer/Chrome runtime, server resources, and maintenance. An API shifts browser setup out of your application and has plan quotas and pricing; ScreenshotNeo lists Free at 1,000 monthly shots, then Starter $5/3,000, Growth $15/15,000, Pro $39/60,000, Scale $99/250,000, and Business $249/1,000,000. Yearly billing gives two months free.

11. Troubleshooting

Symptom Likely cause What to check or change
Node or npm not found The PHP service has a different PATH from your shell. Check the service environment and configure setNodeBinary(), setNpmBinary(), or setIncludePath() with valid paths.
Chrome/Chromium executable not found Puppeteer browser installation is missing or in a different location. Install Chrome as documented for your environment; use setChromePath() if the executable is elsewhere.
Chrome fails to start on Linux A required system library is absent, or the environment restricts Chrome’s sandbox. Follow the distribution-specific dependency instructions in the official requirements page and review the exact Chrome error. Avoid applying system-wide sandbox changes without understanding the host’s security configuration.
Blank or incomplete screenshot The capture happened before client rendering, lazy resources, or fonts finished. Wait for a stable selector or JavaScript condition; use network idle when requests settle, or a deliberate delay for a known animation.
Network-idle wait never completes or takes too long The page polls continuously or holds open connections. Try the less strict waitUntilNetworkIdle(false) or wait for the page element/state that matters.
Element capture fails or selects the wrong element The selector is absent, appears late, or matches multiple elements. Wait for the selector, inspect the page’s actual DOM selector, and set the selector index if you need a later match.
Cannot save the image The destination directory does not exist or is not writable by the PHP process. Use an absolute destination path, create the directory, and check ownership and write permissions for the service account.
Layout differs from a phone or desktop The viewport, device scale, user agent, or mobile emulation does not match the intended presentation. Set the viewport and device/mobile options explicitly; keep scale factor separate from CSS viewport dimensions.

12. Frequently asked questions

Can Browsershot capture HTML that has not been published?

Yes. Pass the HTML string to Browsershot::html($html). Ensure referenced CSS, fonts, and images can be resolved by the browser.

Does Browsershot create PDFs as well as images?

Yes. Browsershot supports PDF output; use a PDF destination and configure paper and related PDF options as needed. See the official PDF documentation.

Why can the same URL produce a different screenshot later?

The page can change its content or rendering based on time, session, viewport, network-loaded assets, or client-side behavior. Control the viewport and wait condition, and provide the appropriate page state when the capture must be repeatable.

Do I need a browser installed on the machine running PHP?

Browsershot relies on Puppeteer controlling Chrome. The browser and its runtime dependencies must be available to the environment executing the capture, whether that is a server, worker, or another configured runtime.

How do I get image data without writing a file?

Browsershot documents base64Screenshot() for returning screenshot data as base64. Consider the memory cost for large full-page captures.