PHP powers over 80% of server-side websites on the internet, from WordPress blogs to enterprise Laravel applications. While PHP excels at server-side rendering and API development, capturing website screenshots programmatically opens powerful possibilities for social media automation, content archiving, visual monitoring, and automated testing.
In this comprehensive guide, we'll explore how to take screenshots in PHP using various methods and libraries. We'll cover everything from basic screenshot capture to advanced techniques like device emulation.
Environment Setup
Before we start capturing screenshots with PHP, let's ensure our development environment is properly configured.
Prerequisites
To follow along with this guide, you'll need:
- PHP 8.x (recommended: PHP 8.1 or higher)
- Composer (PHP package manager)
- Chrome/Chromium (for browser-based solutions)
- Node.js (required for Browsershot/Puppeteer)
Let's verify our installations:
<?php
// Check PHP version
echo "PHP Version: " . phpversion() . "\n";
// Verify Composer is installed (run in terminal)
// composer --version
// Check if Chrome is available
$chromePath = '/usr/bin/google-chrome'; // Linux
// $chromePath = '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome'; // macOS
// $chromePath = 'C:\Program Files\Google\Chrome\Application\chrome.exe'; // Windows
if (file_exists($chromePath)) {
echo "Chrome found at: $chromePath\n";
} else {
echo "Chrome not found. Please install Chrome or Chromium.\n";
}
On Ubuntu/Debian, install Chrome with:
wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add -
sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list'
sudo apt-get update
sudo apt-get install google-chrome-stable
Browsershot - The Standard Solution
Browsershot by Spatie is the most popular PHP screenshot library with over 5 million downloads. It's the de facto standard in the Laravel ecosystem and provides a clean, fluent API for capturing screenshots.Installation
Browsershot requires both a Composer package and Puppeteer (Node.js):
# Install Browsershot via Composer
composer require spatie/browsershot
# Install Puppeteer globally
npm install puppeteer --global
Alternatively, install Puppeteer locally in your project:
npm install puppeteer
Basic Screenshot Capture
Let's capture our first screenshot with Browsershot:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Basic screenshot to file
Browsershot::url('https://web-scraping.dev/products')
->save('products.png');
echo "Screenshot saved successfully!\n";
Browsershot provides a fluent API for configuration. Here's how to customize the output:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Screenshot with custom viewport and format
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080) // Set browser window size
->setScreenshotType('jpeg', 90) // JPEG with 90% quality
->save('products.jpg');
// Save as PNG with custom dimensions
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1280, 720)
->setScreenshotType('png')
->save('products-hd.png');
// Save as WebP for better compression
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->setScreenshotType('webp', 85)
->save('products.webp');
You can also get the screenshot as base64 or binary data instead of saving to a file:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Get screenshot as base64
$base64Image = Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->base64Screenshot();
// Get screenshot as binary data
$imageData = Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->screenshot();
// Save binary data manually
file_put_contents('manual-save.png', $imageData);
đĄ Note: Browsershot requires Node.js and Puppeteer. If you prefer a solution without Node.js dependencies, consider Chrome-PHP or API-based alternatives like Scrapfly's Screenshot API.
Alternative Methods
While Browsershot is the most popular choice, there are other PHP libraries worth considering based on your specific needs.
Chrome-PHP (Pure PHP Solution)
Chrome-PHP is a pure PHP library that controls Chrome directly via the DevTools Protocol, without requiring Node.js. This makes it ideal for environments where installing Node.js is not an option.composer require chrome-php/chrome
Basic screenshot with Chrome-PHP:
<?php
require 'vendor/autoload.php';
use HeadlessChromium\BrowserFactory;
$browserFactory = new BrowserFactory();
// Start Chrome browser
$browser = $browserFactory->createBrowser([
'windowSize' => [1920, 1080],
]);
try {
// Create a new page and navigate
$page = $browser->createPage();
$page->navigate('https://web-scraping.dev/products')->waitForNavigation();
// Take screenshot
$screenshot = $page->screenshot([
'format' => 'png',
]);
// Save to file
$screenshot->saveToFile('chrome-php-screenshot.png');
echo "Screenshot captured with Chrome-PHP!\n";
} finally {
$browser->close();
}
When to choose Chrome-PHP over Browsershot:
- You can't or don't want to install Node.js
- You need direct access to Chrome DevTools Protocol
- You're building for constrained environments
For advanced features like wait strategies and device emulation, refer to the Browsershot examples below, most concepts apply to Chrome-PHP as well.
PuPHPeteer (Puppeteer Bridge)
PuPHPeteer provides a PHP bridge to Puppeteer, giving you full access to Puppeteer's extensive API. Use this when you need Puppeteer-specific features not available in Browsershot.composer require nesk/puphpeteer
npm install @nesk/puphpeteer
Basic screenshot with PuPHPeteer:
<?php
require 'vendor/autoload.php';
use Nesk\Puphpeteer\Puppeteer;
$puppeteer = new Puppeteer();
$browser = $puppeteer->launch([
'args' => ['--no-sandbox', '--disable-setuid-sandbox'],
]);
try {
$page = $browser->newPage();
$page->setViewport(['width' => 1920, 'height' => 1080]);
$page->goto('https://web-scraping.dev/products', [
'waitUntil' => 'networkidle2',
'timeout' => 30000,
]);
$page->screenshot([
'path' => 'puphpeteer-screenshot.png',
'type' => 'png',
'fullPage' => true,
]);
echo "Screenshot captured with PuPHPeteer!\n";
} finally {
$browser->close();
}
When to choose PuPHPeteer over Browsershot:
- You want Puppeteer's full API surface (interactions, CDP tweaks, custom flows)
- You're already comfortable with Puppeteer concepts and options
- You don't mind Node.js being a runtime dependency
Wait Strategies for Dynamic Content
Modern websites heavily rely on JavaScript to render content. Taking a screenshot too early can result in blank or incomplete images. Here's how to handle dynamic content effectively in PHP.
Fixed Delay Approach
The simplest approach is to wait for a fixed amount of time:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Wait 2 seconds for content to load
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->setDelay(2000) // Wait 2000 milliseconds
->save('delayed-screenshot.png');
â ïž Note: Fixed delays are simple but inefficient. They may be too short for slow-loading pages or add unnecessary wait time for fast pages.
Selector-Based Waiting
A smarter approach is to wait for specific elements to appear:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Wait for a specific CSS selector to appear
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForSelector('.product-card') // Wait until product cards load
->save('selector-wait-screenshot.png');
// Wait for multiple elements
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForSelector('.product-card')
->waitForSelector('.pagination')
->save('multi-selector-screenshot.png');
Network Idle Strategy
For JavaScript-heavy Single Page Applications (SPAs), wait for network activity to settle:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Wait until network is idle (no requests for 500ms)
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitUntilNetworkIdle()
->save('network-idle-screenshot.png');
// Alternative: wait for DOM content to be loaded
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForFunction('document.readyState === "complete"')
->save('dom-ready-screenshot.png');
Custom JavaScript Evaluation
For complex scenarios, execute custom JavaScript to determine when the page is ready:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Wait for a custom condition
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForFunction('window.dataLoaded === true', 30000) // 30 second timeout
->save('custom-wait-screenshot.png');
// Wait for specific content to appear
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForFunction('document.querySelectorAll(".product-card").length > 5')
->save('content-loaded-screenshot.png');
Device Emulation & Viewport Control
Capturing screenshots across different devices is essential for responsive design testing and generating platform-specific previews.
Mobile Viewport
Emulate mobile devices by setting appropriate viewport dimensions and user agent:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// iPhone 14 Pro dimensions
Browsershot::url('https://web-scraping.dev/products')
->windowSize(393, 852)
->userAgent('Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1')
->mobile()
->touch() // Enable touch events
->save('mobile-screenshot.png');
// Android device emulation
Browsershot::url('https://web-scraping.dev/products')
->windowSize(412, 915) // Pixel 7 dimensions
->userAgent('Mozilla/5.0 (Linux; Android 13; Pixel 7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Mobile Safari/537.36')
->mobile()
->save('android-screenshot.png');
This approach works by combining a mobile-sized viewport (windowSize) with a realistic userAgent, then enabling mobile emulation via mobile(). Use touch() when you want screenshots to reflect touch-specific UI behavior.
Tablet Viewport
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// iPad Pro dimensions
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1024, 1366)
->userAgent('Mozilla/5.0 (iPad; CPU OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1')
->save('tablet-screenshot.png');
Tablet screenshots are usually about balancing layout breakpoints: many sites switch navigation, grids, and typography around iPad widths. Setting both windowSize() and an iPad user agent helps ensure you capture the tablet-specific responsive layout, not just a resized desktop view.
Retina/High-DPI Screenshots
For crisp screenshots on high-resolution displays, use device scale factor:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// 2x Retina screenshot (double the pixel density)
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->deviceScaleFactor(2) // 2x pixel density
->save('retina-screenshot.png');
// 3x for super high-res displays
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1080, 1920)
->deviceScaleFactor(3)
->save('ultra-hd-screenshot.png');
deviceScaleFactor() controls pixel density: a factor of 2 or 3 increases the number of pixels rendered for the same CSS viewport.
Landscape vs Portrait
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Portrait mode
Browsershot::url('https://web-scraping.dev/products')
->windowSize(393, 852)
->mobile()
->save('portrait-screenshot.png');
// Landscape mode (swap width and height)
Browsershot::url('https://web-scraping.dev/products')
->windowSize(852, 393)
->mobile()
->landscape()
->save('landscape-screenshot.png');
Orientation changes can trigger different breakpoints and UI behaviors (carousels, sticky toolbars, responsive tables, etc.).
Targeting Methods
PHP screenshot libraries offer various methods to capture different parts of a webpage: full page, specific elements, or coordinate-based regions.
Full Page Screenshots
Capture the entire scrollable page, not just the visible viewport:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Full page screenshot (captures entire scrollable content)
Browsershot::url('https://web-scraping.dev/testimonials')
->windowSize(1920, 1080)
->fullPage()
->save('full-page-screenshot.png');
// With wait for lazy-loaded content
Browsershot::url('https://web-scraping.dev/testimonials')
->windowSize(1920, 1080)
->fullPage()
->setDelay(3000) // Wait for lazy-loaded images
->save('full-page-with-lazy-content.png');
fullPage() scrolls and stitches the entire document, which is ideal for long pages like blog posts, product listings, and dashboards. Adding setDelay() can help when images or sections load only after initial render.
Element Screenshots with CSS Selectors
Capture specific elements by their CSS selector:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Screenshot a specific product card
Browsershot::url('https://web-scraping.dev/product/1')
->windowSize(1920, 1080)
->waitForSelector('.product-data')
->select('.product-data') // Target specific element
->save('product-card-screenshot.png');
// Screenshot the navigation header
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForSelector('nav')
->select('nav')
->save('navigation-screenshot.png');
// Screenshot a specific element by ID
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->waitForSelector('#main-content')
->select('#main-content')
->save('main-content-screenshot.png');
Element screenshots are the most reliable way to generate consistent images for documentation, cards, or regression tests because they avoid layout changes outside the target area.
Coordinate-Based Clipping
For precise control, use coordinate-based clipping:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
// Clip a specific region (x, y, width, height)
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->clip(100, 100, 800, 600) // x=100, y=100, width=800, height=600
->save('clipped-screenshot.png');
// Capture just the top banner area
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->clip(0, 0, 1920, 200) // Top 200 pixels
->save('banner-screenshot.png');
// Capture center of the page
Browsershot::url('https://web-scraping.dev/products')
->windowSize(1920, 1080)
->clip(460, 200, 1000, 600)
->save('center-region-screenshot.png');
Clipping is useful when you know exactly which screen region you want (e.g., a hero banner area) or when selectors are unstable.
Scrapfly Screenshot API
For production environments where you want to avoid managing browser dependencies, consider using Scrapfly's Screenshot API. It handles the infrastructure complexity and provides additional features for enterprise-scale screenshot capture.
ScrapFly provides web scraping, screenshot, and extraction APIs for data collection at scale.
- Anti-bot protection bypass - screenshot web pages without blocking!
- Rotating residential proxies - prevent IP address and geographic blocks.
- JavaScript rendering - screenshot dynamic web pages through cloud browsers.
- Full screenshot customization - scroll and capture exact areas.
- Comprehensive options - block banners, use dark mode, and more.
- Python and Typescript SDKs, as well as Scrapy and no-code tool integrations.
Using cURL
If you want a dependency-free integration, cURL is the simplest way to call Scrapflyâs Screenshot API and save the image bytes directly to disk.
<?php
// Scrapfly Screenshot API with cURL
$apiKey = 'YOUR_SCRAPFLY_API_KEY';
$targetUrl = 'https://web-scraping.dev/products';
$params = http_build_query([
'key' => $apiKey,
'url' => $targetUrl,
'format' => 'png',
'resolution' => '1920x1080',
'rendering_wait' => 2000,
'options' => 'block_banners',
]);
$ch = curl_init();
curl_setopt_array($ch, [
CURLOPT_URL => "https://api.scrapfly.io/screenshot?{$params}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => true,
]);
$imageData = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode === 200) {
file_put_contents('scrapfly-screenshot.png', $imageData);
echo "Screenshot saved successfully!\n";
} else {
echo "Error: HTTP {$httpCode}\n";
}
This example builds the query string with http_build_query(), executes the request, and writes the response body as a PNG file.
Using Guzzle with Anti-Bot Bypass
Guzzle is a good fit when youâre already using it in your PHP app and want cleaner HTTP code, plus advanced Scrapfly options like anti-bot bypass, caching, and selector-based waiting.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
$apiKey = 'YOUR_SCRAPFLY_API_KEY';
// Screenshot with anti-bot bypass and banner blocking
$response = $client->get('https://api.scrapfly.io/screenshot', [
'query' => [
'key' => $apiKey,
'url' => 'https://web-scraping.dev/products',
'format' => 'png',
'resolution' => '1920x1080',
'asp' => 'true', // Anti-Scraping Protection bypass
'options' => 'block_banners',
'wait_for_selector' => '.product-card',
'country' => 'us', // Use US proxy
],
]);
if ($response->getStatusCode() === 200) {
file_put_contents('scrapfly-bypass-screenshot.png', $response->getBody());
echo "Screenshot with anti-bot bypass saved!\n";
}
// Full page screenshot with caching
$response = $client->get('https://api.scrapfly.io/screenshot', [
'query' => [
'key' => $apiKey,
'url' => 'https://web-scraping.dev/testimonials',
'format' => 'png',
'resolution' => '1920x1080',
'full_page' => 'true',
'cache' => 'true', // Enable caching
'cache_ttl' => 3600, // Cache for 1 hour
],
]);
file_put_contents('scrapfly-fullpage.png', $response->getBody());
The first request demonstrates asp=true (anti-bot bypass) plus wait_for_selector to capture the page after a key element renders.
Mobile Screenshot with Scrapfly
To generate mobile previews, set a phone-like resolution and choose a compact format like WebP to keep file sizes low.
<?php
require 'vendor/autoload.php';
use GuzzleHttp\Client;
$client = new Client();
$apiKey = 'YOUR_SCRAPFLY_API_KEY';
// Mobile viewport screenshot
$response = $client->get('https://api.scrapfly.io/screenshot', [
'query' => [
'key' => $apiKey,
'url' => 'https://web-scraping.dev/products',
'format' => 'webp', // Smaller file size
'resolution' => '393x852', // iPhone 14 Pro dimensions
'rendering_wait' => 2000,
],
]);
file_put_contents('mobile-scrapfly.webp', $response->getBody());
echo "Mobile screenshot saved!\n";
This uses a mobile resolution (iPhone-sized) and a short rendering_wait to give the page time to paint before capture.
FAQ
How do I take a screenshot of a URL in PHP?
The simplest way to screenshot a URL in PHP is using Browsershot:
<?php
require 'vendor/autoload.php';
use Spatie\Browsershot\Browsershot;
Browsershot::url('https://example.com')->save('screenshot.png');
For production use without managing browser dependencies, use Scrapfly's Screenshot API with a simple HTTP request.
What is the best PHP library for screenshots?
Browsershot by Spatie is the most popular and well-maintained PHP screenshot library. It has over 5 million downloads and integrates seamlessly with Laravel. However, it requires Node.js and Puppeteer. If you need a pure PHP solution, Chrome-PHP is an excellent alternative that communicates directly with Chrome via DevTools Protocol.
How do I capture full-page screenshots in PHP?
Use the fullPage() method in Browsershot:
<?php
Browsershot::url('https://example.com')
->fullPage()
->save('full-page.png');
This captures the entire scrollable content, not just the visible viewport.
Why is my screenshot blank or incomplete?
This usually happens because JavaScript content hasn't loaded yet. Use wait strategies:
<?php
// Wait for selector
Browsershot::url('https://example.com')
->waitForSelector('.content')
->save('screenshot.png');
// Or wait for network idle
Browsershot::url('https://example.com')
->waitUntilNetworkIdle()
->save('screenshot.png');
What's the difference between Browsershot and Chrome-PHP?
| Feature | Browsershot | Chrome-PHP |
|---|---|---|
| Node.js Required | Yes | No |
| Underlying Engine | Puppeteer | Chrome DevTools Protocol |
| API Style | Fluent/chainable | Object-oriented |
| Laravel Integration | Excellent | Manual |
| Community Support | Very large | Moderate |
Choose Browsershot for Laravel projects or when you want a simple, well-documented API. Choose Chrome-PHP when you can't install Node.js or need direct DevTools Protocol access.
Conclusion
Taking screenshots in PHP is mostly about choosing the right tool for your environment: Browsershot is the most convenient option especially in Laravel, Chrome-PHP is great when you canât ship Node.js, PuPHPeteer is ideal when you need the full Puppeteer API, and Scrapflyâs Screenshot API is the simplest path when you want a managed, production-ready renderer that can handle protected pages.
Whichever route you choose, the difference between flaky and reliable screenshots usually comes down to the same fundamentals: use the right wait strategy, target the correct region. If youâre generating screenshots at scale, add caching and consistent settings so your outputs are stable and repeatable.