Screenshot Webpages as PNG in PHP
Learn three reliable ways to capture webpages as PNG in PHP: Browsershot, chrome-php/chrome, and a hosted screenshot API.

To screenshot a webpage as a PNG in PHP, use a real browser rendering engine. A browser evaluates HTML, CSS, JavaScript, fonts and images before it captures pixels; downloading the HTML or using an image library alone will not reproduce a modern page.
You have three practical approaches:
- Browsershot drives headless Chrome through Puppeteer and provides a convenient PHP API.
- chrome-php/chrome controls Chrome directly when you need lower-level browser operations.
- A hosted screenshot API renders the page outside your PHP process, so your application does not manage Chrome.
This guide shows complete PNG examples, full-page captures, viewport and device settings, dynamic content waits, authentication, failure handling, and production considerations.
1. Capture a PNG with Browsershot
Browsershot is a PHP package that uses Puppeteer to control headless Google Chrome. Its image API uses PNG by default, so the basic URL-to-file operation is short:

<?php
require __DIR__ . '/vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com')
->save(__DIR__ . '/example.png');
Install the package with Composer, then install and configure the Node.js, Puppeteer and Chrome components required by the current Browsershot release. Check the package’s live installation documentation for compatible versions and executable paths before deploying.
composer require spatie/browsershot
Set the viewport and capture the full page
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com')
->windowSize(1440, 900)
->fullPage()
->save(__DIR__ . '/example-full.png');
A normal capture records the visible viewport. fullPage() asks the browser to include the document’s complete scrollable height. Full-page mode is useful for documentation, invoices and visual regression snapshots, but very long pages can produce large images and consume more memory.
Wait for JavaScript and lazy content
Single-page applications often render after the initial navigation event. Browsershot documents delays and waits for selectors or page resources. Wait for a stable element instead of relying only on an arbitrary sleep when possible.
<?php
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com/dashboard')
->windowSize(1366, 768)
->waitForSelector('.dashboard-loaded')
->delay(500)
->save(__DIR__ . '/dashboard.png');
Use a delay for animations, chart drawing or third-party widgets that have no reliable selector. Keep the delay as short as the page allows. If the page depends on network requests, use the package’s documented wait-for-resource or JavaScript-function options and verify the exact method names for your installed version.
Other useful Browsershot controls
- Device emulation: capture mobile layouts with a documented device preset.
- Device scale factor: increase pixel density for retina-style output.
- Background handling: preserve or omit backgrounds according to the image API options.
- HTML input: render an HTML string instead of navigating to a URL.
- Selectors and clipping: capture a specific element when a full page is unnecessary.
PNG is lossless and preserves text and sharp UI edges. If file size matters more than lossless output, consult the image API for JPEG or WebP options.
2. Control Chrome directly with chrome-php/chrome
chrome-php/chrome is appropriate when you need direct access to browser and page operations. The documented flow starts Chrome, opens a page, waits for navigation, and saves a screenshot. PNG is the default format.
<?php
require __DIR__ . '/vendor/autoload.php';
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
$browser = $browserFactory->createBrowser([
'headless' => true,
]);
try {
$page = $browser->createPage();
$page->navigate('https://example.com')->waitForNavigation();
$page->screenshot()->saveToFile(__DIR__ . '/example.png');
} finally {
$browser->close();
}
Install the package with Composer and follow its current Chrome requirements. Keep the browser lifecycle inside a try/finally block so crashed requests do not leave orphaned Chrome processes.
Capture the complete page
<?php
$page->navigate('https://example.com')->waitForNavigation();
$clip = $page->getFullPageClip();
$page->screenshot([
'captureBeyondViewport' => true,
'clip' => $clip,
])->saveToFile(__DIR__ . '/example-full.png');
The documented getFullPageClip() pattern calculates the page’s full dimensions. For a viewport screenshot, omit the clip and capture-beyond-viewport setting. The library also documents JPEG and WebP alternatives, but leaving the format unspecified produces PNG.
When direct control is useful
Choose direct Chrome control when you need browser primitives such as custom navigation sequencing, multiple pages in one browser session, or lower-level DevTools operations. You must still operate the Chrome binary, process limits, sandbox settings and package compatibility yourself.
3. Hosted PHP screenshot APIs
A hosted API moves browser management out of your PHP application. Your code sends a URL and options, receives image bytes, and writes them to storage. This can simplify deployment when installing Chrome is difficult, but review the provider’s current options, limits and data-handling terms before production use.
ScreenshotOne documents a PHP SDK that returns image data and supports PNG:
<?php
require __DIR__ . '/vendor/autoload.php';
$client = new ScreenshotOne\ScreenshotOne(
'YOUR_ACCESS_KEY',
'YOUR_SECRET_KEY'
);
$image = $client->capture([
'url' => 'https://example.com',
'format' => 'png',
'full_page' => true,
'delay' => 2,
]);
file_put_contents(__DIR__ . '/example.png', $image);
Use the provider’s current SDK documentation for the exact package name, authentication fields and option names. A PNG response is binary data; do not pass it through JSON encoding or treat it as UTF-8 text.
4. Production options you should decide explicitly
| Decision | Viewport capture | Full-page capture |
|---|---|---|
| Output size | Predictable width and height | Height grows with document length |
| Best for | Cards, thumbnails, dashboards | Articles, docs, receipts |
| Main risk | Content below the fold is omitted | Large memory use and very tall PNGs |
Authentication and private pages
For pages behind authentication, use a session cookie, an authorization header, or a dedicated signed URL. Never place long-lived credentials in a public query string. With local Chrome, create the authenticated context before navigation. With a hosted API, use the provider’s documented custom-header and cookie options and redact secrets from logs.
Fonts, images and lazy loading
A screenshot is only as complete as the resources loaded before capture. Web fonts can change line wrapping after the first paint. Lazy images may not load until scrolled into view. Full-page implementations that support lazy loading are preferable for long documents; otherwise, scroll through the page or trigger the site’s loading mechanism before capturing.
Selectors, overlays and consent banners
Cookie notices, chat bubbles and newsletter modals can cover content. In a self-managed browser, click the consent button or hide known selectors before the screenshot. Make this deterministic: wait for the overlay, interact with it, then wait for the underlying content to settle.
Security boundaries
Do not let arbitrary users submit unrestricted URLs to a browser running inside your private network. Validate schemes, restrict internal address ranges, isolate browser workers, apply timeouts, and limit output dimensions. A screenshot endpoint can otherwise become a server-side request forgery path.
5. Error handling and troubleshooting
Chrome executable not found
Cause: Chrome or Chromium is not installed, or the library cannot locate it.
Fix: install a supported browser and set the executable path using the package’s documented configuration. Confirm the PHP worker user can execute it.
Sandbox or permission errors
Cause: container or shared-host restrictions prevent Chrome from creating its profile or sandbox.
Fix: provide a writable temporary directory, run with an appropriate user, and follow your deployment platform’s Chrome guidance. Avoid disabling security controls unless your isolation model explicitly requires it.
Blank or partially rendered image
Cause: capture occurs before JavaScript, fonts or images finish loading.
Fix: wait for a stable selector, network idle or a short delay; increase navigation and capture timeouts; inspect browser logs; and test the URL from the same server environment.
Timeouts
Cause: slow third-party resources, an infinite-loading request, or an unreachable host.
Fix: set a finite navigation timeout, block nonessential resources, and retry transient failures with exponential backoff. Do not retry indefinitely.
Images differ between local and production
Cause: different Chrome versions, fonts, timezone, locale, device scale, viewport or authenticated state.
Fix: pin the browser environment, set viewport and timezone explicitly, install the same fonts, and capture from the same deployment image used in CI.
PNG cannot be opened
Cause: an error page or JSON response was saved as if it were image bytes.
Fix: check HTTP status and content type before writing the body. For APIs, log response headers and a bounded error body, never the entire binary payload.
6. Performance, reliability and cost
Launching a fresh browser for every request is simple but expensive. A worker pool that reuses a browser process can reduce startup overhead, while separate pages or contexts keep jobs isolated. Recycle browsers after a bounded number of captures to limit memory growth.
Use caching for repeat URLs, but include all visual inputs in the cache key: URL, viewport, device scale, full-page flag, theme, authentication state and custom CSS. Set a TTL that matches how often the source changes.
PNG files are larger than JPEG or WebP. Store them with a content hash, send them through object storage, and set a retention policy. For visual tests, deterministic PNGs are valuable; for user-facing previews, a compressed format may be cheaper to transfer.
Measure queue time, browser startup time, navigation time, capture time, output bytes and failure reason. The supplied research does not establish neutral benchmarks or universal cost winners among Browsershot, chrome-php/chrome and hosted services, so choose using your own page mix and operational constraints.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP or PDF. Its capture flow accepts cookie and consent banners before the shot, then removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled.

Only clean shots are billed. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers identify the page verdict and billing status.
See the ScreenshotNeo API documentation for all options.
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,
)
r.raise_for_status()
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(`HTTP ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await Bun.write('shot.webp', buffer);
ScreenshotNeo supports full-page capture with lazy images loaded, CSS-element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper and page controls, HTML/CSS-to-image, custom CSS and JavaScript, clicks, selector hiding, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, configurable caching, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, which helps when switching.
An MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients. Plans include 1,000 screenshots per month free with no card; paid plans start at $5 for 3,000 shots. Yearly billing gives two months free, and every feature is on every plan.
Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no cost and no card.
8. PHP implementation checklist
- Choose viewport or full-page output deliberately.
- Pin compatible PHP, browser and package versions.
- Set navigation, selector and overall job timeouts.
- Wait for fonts, charts, images and application data.
- Set viewport, device scale, locale and timezone explicitly.
- Protect private URLs and prevent server-side request forgery.
- Validate status and content type before saving bytes.
- Reuse browser workers carefully and recycle them periodically.
- Cache deterministic captures with a complete visual-input key.
- Record failure reasons, output size and timing metrics.
FAQ
Can PHP create a PNG without Chrome?
Only for simple, already-rendered image data. A faithful webpage screenshot needs a browser engine or a service that runs one.
Is PNG better than JPEG for screenshots?
PNG preserves sharp text and UI edges without lossy artifacts. JPEG or WebP can be smaller for photographic pages or previews.
Why is my full-page image extremely tall?
Full-page mode includes the document’s complete scroll height. Capture a selected element, limit the page, or use viewport mode when a single screen is sufficient.
Should I use a local browser or an API?
Use a local browser when you need process-level control and can operate Chrome reliably. Use an API when you prefer to delegate browser installation and rendering operations.


