ScreenshotNeo

BlogHow-to

Take a Screenshot of a Webpage with PHP, Guzzle, and Headless Chrome

Use PHP to capture a webpage with headless Chrome. Learn what Guzzle does, install the browser library, save viewport or full-page images, and troubleshoot failures.

By the ScreenshotNeo team4 October 20268 min read

Short answer: Use chrome-php/chrome to control headless Chrome or Chromium from PHP, navigate to the page, then call the page screenshot method and save the image. Guzzle is an HTTP client; it does not render webpages or take the screenshot in this flow. You can use Guzzle separately for API calls or other HTTP work in the same application.

This guide covers a local Chrome-based capture, viewport and full-page choices, errors, and operational considerations. The library’s repository lists PHP 7.4–8.5 and Chrome/Chromium 65+ as requirements; check the documentation for the version you install because compatibility can change. See the chrome-php/chrome repository and Guzzle documentation.

1. Understand the roles of PHP, Guzzle, and Chrome

A screenshot requires a browser rendering engine. Chrome loads HTML, applies CSS, runs JavaScript, and paints the page. The PHP package chrome-php/chrome starts and controls Chrome or Chromium and exposes page navigation and screenshot methods.

  • Chrome/Chromium: renders the page and produces the image.
  • chrome-php/chrome: controls the browser from PHP.
  • Guzzle: sends HTTP requests from PHP. It can call an API or fetch ordinary resources, but it is not the browser controller in this example.

If the goal is a screenshot of a JavaScript-rendered webpage, an HTTP response body from Guzzle alone is insufficient: downloading the HTML does not execute its scripts or apply browser layout.

2. Install the PHP package and browser

In a Composer-managed project, install the browser-control library:

composer require chrome-php/chrome

Install Chrome or Chromium on the machine that runs PHP. The executable must be available to the process, either on its PATH or at a path you provide to the browser factory. The project README lists Chrome/Chromium 65+ and PHP 7.4–8.5; treat those as project-stated requirements for the documented project, and verify them against the release you use.

The project says it is tested on Linux and compatible with macOS and Windows. Headless browser installation and executable paths differ by operating system and deployment image, so confirm the browser starts under the same user and environment as your PHP worker.

3. Capture a viewport screenshot

This complete PHP example opens a page, waits for navigation, captures the current viewport as PNG, and always closes the browser. Replace the URL and output location as needed.

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

use HeadlessChromium\BrowserFactory;

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

$browserFactory = new BrowserFactory();
$browser = $browserFactory->createBrowser([
    'headless' => true,
]);

try {
    $page = $browser->createPage();
    $navigation = $page->navigate($url);
    $navigation->waitForNavigation();

    // PNG is the default screenshot format.
    $page->screenshot()->saveToFile($output);
} finally {
    $browser->close();
}

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

The navigation wait prevents taking the screenshot immediately after issuing the navigation command. It does not guarantee that every delayed image, animation, or application-specific data request has finished; choose a wait strategy that matches the page.

Set the viewport size

Viewport dimensions affect responsive breakpoints and what appears in a viewport capture. Set the viewport before navigating or capturing using the page API supported by your installed library version. For example, the package exposes viewport sizing through its page API; consult the project documentation for the exact method and arguments in the installed release. Make the width and height explicit when screenshots must be repeatable.

4. Choose viewport, clipped-region, or full-page capture

Capture type What it includes Use it when
Viewport The visible browser viewport You need a consistent above-the-fold image or a responsive-layout check.
Clipped region A selected rectangular area of the rendered page You need a component or specific region rather than the whole viewport.
Full page The page beyond the current viewport You need a long-page capture, such as an article or landing page.

The library documents region clipping and full-page capture with getFullPageClip plus captureBeyondViewport. The following illustrates the documented full-page configuration; check the method signatures for your installed release:

// After navigating and waiting for the page:
$clip = $page->getFullPageClip();
$page->screenshot([
    'clip' => $clip,
    'captureBeyondViewport' => true,
])->saveToFile(__DIR__ . '/full-page.png');

For a clipped region, pass a clip rectangle to the screenshot method using the format documented by your installed version. Make sure the rectangle is within the rendered page bounds. Full-page capture can be much taller and consume substantially more memory than a viewport image, especially on long pages or at high device scale factors.

5. Select an output format

PNG is the documented default. The library also documents JPEG and WebP. Choose PNG for lossless output and sharp text; choose JPEG or WebP when smaller files matter and lossy compression is acceptable. Set the screenshot format and any quality option using the API options supported by your installed release. Confirm the extension matches the chosen encoding.

Screenshot dimensions depend on the viewport and any device scale factor. A high-resolution capture increases pixel count and output size. Start with the smallest dimensions that satisfy the consuming workflow.

6. Where Guzzle fits

Use Guzzle for separate HTTP tasks, such as calling your own API before capture, submitting a screenshot result afterward, or retrieving metadata. Keep the browser navigation and screenshot work in chrome-php/chrome.

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

$client = new \GuzzleHttp\Client(['timeout' => 20]);
$response = $client->get('https://example.com/api/status');

if ($response->getStatusCode() !== 200) {
    throw new RuntimeException('Status API returned an unexpected response.');
}

$status = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);
// Use $status in your application; Chrome capture remains a separate operation.

Guzzle supports configurable handlers, and its FAQ describes handlers other than cURL, including PHP’s stream wrapper. Handler requirements depend on the operation and installed version; the older Guzzle overview notes cURL is required for concurrent requests. Do not assume a handler choice makes browser rendering faster or changes how Chrome captures a page.

7. Production considerations

Wait for the right page state

Navigation completion is a useful baseline, but single-page applications may continue rendering after the initial navigation. If the screenshot must include a specific component or data state, wait for that condition using the page-wait features supported by your library version. A fixed delay is simple but can waste time on fast pages and still be too short on slow ones.

Close browser resources reliably

Always close the browser in a finally block or equivalent cleanup path. Browser processes consume memory and other system resources; abandoned processes can accumulate in long-lived workers.

Control concurrency and resource use

Each active browser and page adds resource use. Limit concurrent captures based on the memory and CPU available to the worker. Full-page shots and high-resolution output need more memory than viewport shots. If captures run in a queue, set bounded worker concurrency and make sure failed jobs still trigger cleanup.

Make failures observable

Log the target host, capture stage, exception details, and elapsed time. Avoid logging secrets in query strings or headers. Distinguish browser startup failures, navigation failures, timeouts, and file-write errors so retries target the cause rather than repeating every failure blindly.

Protect the capture service

If users can submit URLs, validate destinations and restrict access to internal networks and sensitive services. A browser that can visit arbitrary URLs can otherwise be used to reach resources the application itself should not expose. Apply outbound network controls as well as application-level validation.

8. Troubleshooting

Symptom Likely cause What to do
Chrome executable not found Chrome/Chromium is not installed, or PHP cannot find its executable. Install a supported browser and configure the executable path in the browser factory for your package version. Check the PHP worker’s PATH.
Browser exits immediately or cannot start Missing system dependencies, permissions, or environment differences between CLI and worker. Run under the same user as the application worker; inspect browser startup output and install the OS dependencies required by that browser build.
Navigation wait times out The page is slow, unreachable, or keeps network activity open; the wait condition may not match the site. Check the URL from the worker environment. Adjust the navigation timeout or wait for a page-specific condition supported by the library.
Screenshot is blank or incomplete Capture happened before client-side rendering, the page returned an error, or content is below the viewport. Wait for the relevant element or rendering state. Use full-page capture for content beyond the viewport and inspect the page’s actual browser state.
Output file is missing Destination directory does not exist or PHP lacks write permission. Create the directory and grant the PHP process write access. Check the exact absolute output path.
Image is unexpectedly small or large Viewport, device scale, capture scope, or format differs from expectation. Set viewport dimensions explicitly, inspect the chosen capture mode, and verify format/scale options for the installed package version.
Works locally but fails in deployment Browser binary, libraries, permissions, fonts, or environment differ. Install the browser in the deployment image and run a smoke capture as the same user and with the same environment as the PHP worker.

9. Or skip the browser setup

If you do not want to install and operate Chrome for a screenshot job, ScreenshotNeo provides a website screenshot API and MCP server. A GET request returns a PNG, JPEG, WebP, or PDF; see the ScreenshotNeo API documentation.

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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const bytes = new Uint8Array(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', bytes));

Cookie banners, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000.

Sign up free and get 1,000 screenshots a month with no card.

10. Frequently asked questions

Can Guzzle take a screenshot by itself?

No. Guzzle makes HTTP requests; it does not execute a browser rendering engine. Use Chrome controlled through chrome-php/chrome for this approach.

Do I need Chrome installed on the PHP server?

Yes, for the local browser approach. The PHP library controls a Chrome or Chromium executable that must be available to the process.

Can this capture a JavaScript-heavy page?

Yes, because Chrome runs page JavaScript, but choose a wait condition that reflects when the content you need has rendered.

Which format should I use?

PNG is the default and suits sharp, lossless screenshots. JPEG or WebP can reduce file size when lossy output is acceptable.

Sources