How to Capture Webpages as WebP Images in PHP
Render a webpage with Chromium, save it as WebP, validate GD support, troubleshoot failures, and use ScreenshotNeo when you want a managed API.
Direct answer: PHP cannot render a webpage with GD alone. Use a browser engine such as Chrome or Chromium to load the HTML, CSS and JavaScript, request a WebP screenshot, and save the returned bytes. PHP’s imagewebp() function only encodes an image that already exists; it does not open a URL or run a browser.
This guide covers a self-managed Chromium workflow, GD encoding and validation, full-page and dynamic pages, deployment concerns, troubleshooting, and a managed alternative with ScreenshotNeo.
1. Choose a rendering method
| Method | What it does | Use it when |
|---|---|---|
| PHP-controlled Chrome/Chromium | Loads the page and captures rendered pixels as WebP | You need browser control, custom waiting, cookies, headers or local rendering |
| Hosted screenshot API | A remote browser renders the URL and returns an image | You want to avoid installing and maintaining browsers |
PHP GD imagewebp() |
Encodes an existing GD image as WebP | You already have pixels from another source |
Rendering and encoding are separate steps. A browser produces pixels; WebP is the output format. Keep that distinction in mind when diagnosing failures.
2. Capture a page with Chromium from PHP
The chrome-php/chrome project documents screenshot formats including png, jpeg and webp, quality settings, and full-page capture. Follow its current installation instructions and confirm that the installed package matches your PHP and browser versions.
Install the PHP package and browser
composer require chrome-php/chrome
Install Chrome or Chromium through your operating system or container image. In production, pin and update the browser deliberately; browser binaries, sandbox settings and shared libraries are deployment dependencies.
Minimal WebP screenshot
<?php
require __DIR__ . '/vendor/autoload.php';
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
$browser = $browserFactory->createBrowser([
// Set this when Chrome is not on PATH:
// 'customFlags' => ['--no-sandbox'],
]);
try {
$page = $browser->createPage();
$page->navigate('https://example.com')->waitForNavigation();
// The package documentation defines the supported screenshot options.
$page->setScreenshotOptions([
'format' => 'webp',
'quality' => 80,
]);
$page->screenshot()->saveToFile(__DIR__ . '/example.webp');
$path = __DIR__ . '/example.webp';
if (!is_file($path) || filesize($path) === 0) {
throw new RuntimeException('Screenshot file is missing or empty');
}
} finally {
$browser->close();
}
Use the package’s current API reference for the exact browser flags and screenshot method available in your installed release. Treat quality as an output-size and fidelity choice: lower values usually produce smaller files, while higher values preserve more detail.
Wait for application content
<?php
$page->navigate('https://example.com/dashboard')->waitForNavigation();
// Wait for a page-specific element when your application renders asynchronously.
$page->waitFor('#dashboard');
// A short delay can cover animations or data that appears after the selector.
usleep(500000);
$page->setScreenshotOptions([
'format' => 'webp',
'quality' => 82,
]);
$page->screenshot()->saveToFile(__DIR__ . '/dashboard.webp');
Prefer a meaningful selector over an arbitrary long sleep. A selector tells you that the required component exists; a delay only guesses how long the page needs.
Full-page capture
<?php
$page->navigate('https://example.com/article')->waitForNavigation();
$page->setScreenshotOptions([
'format' => 'webp',
'quality' => 80,
'captureBeyondViewport' => true,
]);
$page->screenshot()->saveToFile(__DIR__ . '/article-full.webp');
Full-page screenshots can be much taller than a viewport image. Check the target page height, browser memory limits and output file size before enabling this for arbitrary URLs.
3. Control the viewport and page state
Set a viewport that matches the layout you want to document. Responsive breakpoints can change navigation, columns and image sizes, so a screenshot is only reproducible when the viewport and page state are reproducible.
<?php
$page->setViewport(1440, 900);
$page->navigate('https://example.com')->waitForNavigation();
$page->screenshot([
'format' => 'webp',
'quality' => 80,
])->saveToFile(__DIR__ . '/desktop.webp');
For pages that depend on login state, configure cookies or authentication in the browser session before navigation. For deterministic output, disable or wait for animations, ensure web fonts have loaded, and avoid capturing while content is still shifting.
4. Use GD when the screenshot already exists
PHP documents imagewebp() as a function that “Outputs or saves a WebP version of the given image.” It accepts a GD image and a destination path or stream. Quality is documented from 0 to 100; -1 uses the documented default of 80.
<?php
$source = __DIR__ . '/input.png';
$output = __DIR__ . '/output.webp';
$image = imagecreatefromstring(file_get_contents($source));
if ($image === false) {
throw new RuntimeException('GD could not decode the source image');
}
try {
if (!imagewebp($image, $output, 80)) {
throw new RuntimeException('GD reported a WebP encoding failure');
}
} finally {
imagedestroy($image);
}
// The PHP manual warns that a true return value may not prove that libgd
// actually wrote valid output, so verify the file independently.
if (!is_file($output) || filesize($output) === 0) {
throw new RuntimeException('WebP output is missing or empty');
}
$check = imagecreatefromwebp($output);
if ($check === false) {
throw new RuntimeException('Output is not readable as WebP');
}
imagedestroy($check);
WebP input support depends on your PHP build and linked libgd. imagecreatefromstring() only detects WebP when that build supports it; WebP support was added in PHP 7.3 when the linked libgd supports the format. imagecreatefromwebp() cannot read animated WebP files.
5. Validate the runtime before production
<?php
if (!extension_loaded('gd')) {
throw new RuntimeException('GD extension is not loaded');
}
$info = gd_info();
if (empty($info['WebP Support'])) {
throw new RuntimeException('This GD build does not report WebP support');
}
echo "GD WebP support is available\n";
- Check the PHP version used by the web worker or queue, not only the CLI binary.
- Check the actual libgd capabilities in the production image.
- Write a temporary WebP, decode it, and remove it during a deployment health check.
- Keep enough disk space for full-page images and concurrent browser sessions.
6. Common errors and fixes
| Error or symptom | Likely cause | Fix |
|---|---|---|
imagewebp() is undefined |
GD is not installed or enabled | Enable the GD extension in the PHP runtime that executes the job. |
| WebP output is empty or unreadable | libgd lacks WebP support, or the write failed | Check gd_info(), verify the path is writable, then decode the resulting file. |
| Browser cannot start | Chrome/Chromium is missing, not executable, or required shared libraries are absent | Install a compatible browser, set its executable path, and inspect the process error output. |
| Sandbox permission error | Container or service account cannot use the browser sandbox | Fix the container permissions first; only use the package’s documented flags when your deployment requires them. |
| Screenshot is blank | Capture happened before navigation or client-side rendering finished | Wait for navigation and a page-specific selector; investigate JavaScript errors and blocked resources. |
| Images or fonts are missing | Lazy loading, network blocking, authentication or cross-origin failures | Scroll or wait for the required content, check browser network errors, and provide the needed cookies or headers. |
| Page is cut off | Viewport capture was used for a long page | Enable the library’s full-page option and monitor page height and memory use. |
| Animated WebP cannot be read by GD | imagecreatefromwebp() does not read animated WebP |
Use a non-animated frame or another tool that supports animated input. |
Quality argument throws a ValueError |
PHP 8.4 validates the quality range | Pass an integer from 0 through 100, or use -1 for the documented default. |
7. Performance, reliability and cost
- Reuse browsers carefully: starting Chromium for every request adds startup cost. A long-lived browser can improve throughput, but isolate pages and restart the process when it becomes unhealthy.
- Limit concurrency: each page consumes CPU and memory. Queue captures and apply a per-worker limit instead of allowing unbounded parallel browsers.
- Bound waits: use navigation and selector timeouts so a page that never finishes cannot occupy a worker forever.
- Control output size: choose the smallest viewport and WebP quality that meets your use case. Full-page captures and high quality increase memory, transfer and storage costs.
- Retry selectively: retry transient navigation or network failures with a limit and backoff. Do not blindly retry deterministic HTTP errors or invalid URLs.
- Record diagnostics: log the URL, viewport, browser version, elapsed time, output bytes and failure stage. Avoid logging credentials or private page contents.
Self-hosting moves browser installation, patching, capacity planning and observability into your system. A hosted service removes that infrastructure, but you must evaluate its current pricing, limits, privacy terms and reliability for your workload.
8. Or skip the browser setup
ScreenshotNeo provides a website screenshot API with a PHP-friendly HTTP interface. It renders the page remotely and can return WebP, PNG, JPEG or PDF. Before capture it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and the response reports the result with X-Page-Verdict and X-Billed headers.
See the ScreenshotNeo API documentation for the complete option list. The basic call is:
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
PHP
<?php
$url = 'https://stripe.com';
$query = http_build_query([
'access_key' => 'YOUR_API_KEY',
'url' => $url,
]);
$context = stream_context_create([
'http' => [
'timeout' => 90,
'ignore_errors' => true,
],
]);
$body = file_get_contents("https://api.screenshotneo.com/v1/shot?$query", false, $context);
if ($body === false || $body === '') {
throw new RuntimeException('ScreenshotNeo returned no image bytes');
}
file_put_contents(__DIR__ . '/shot.webp', $body);
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
r.raise_for_status()
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}`);
ScreenshotNeo also supports full-page and element capture, dark mode, device presets, custom viewport and retina scale, PDF options, custom CSS and JavaScript, clicks, waits, blocked requests, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.
The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 shots, and every feature is available on every plan. Create a free ScreenshotNeo account.
9. Practical checklist
- Use Chromium or another browser engine to render the URL.
- Request
webpfrom the screenshot library when it supports that format. - Wait for navigation and page-specific asynchronous content.
- Use full-page capture only when the output height and memory budget allow it.
- Confirm GD WebP support in the production PHP build.
- Verify output existence, size and independent decodability.
- Set timeouts, concurrency limits and bounded retries.
- Protect cookies, authorization headers and private screenshot files.
10. FAQ
Can PHP GD screenshot a URL directly?
No. GD encodes and manipulates existing pixels. A browser engine must load and render the webpage first.
Is WebP always smaller than PNG?
Not for every image or quality setting. Measure representative pages and choose the format and quality that meet your visual and storage requirements.
Can I capture pages that require JavaScript?
Yes, when using a real browser. Wait for the application state you need before taking the screenshot.
Why does my local GD test pass but production fail?
PHP binaries can be built with different libgd versions and capabilities. Check the runtime that actually handles production requests.
When should I use a hosted API?
Use one when browser installation, upgrades, scaling and failure handling are more work than you want inside your PHP service, after reviewing the provider’s current terms and data handling.


