Tools: How to Take a Website Screenshot with PHP (2026)

Tools: How to Take a Website Screenshot with PHP (2026)

Installing Node.js

Creating the project and installing Puppeteer

Writing the screenshot script

Connecting it to PHP

Adding options

There's a simpler way I want to walk you through the full process of taking website screenshots from PHP today. Not theory, not a library overview -- the actual thing. Open a terminal, install dependencies, write code, run it, get a PNG file with a screenshot. The whole path from an empty folder to a working result. Why would you even need this? Link previews for a directory site, automated OG images, screenshots for client reports, visual monitoring. Sooner or later a PHP project needs to turn a URL into an image. The problem is that PHP can't render web pages on its own. There's no browser engine built in. So we'll need a helper -- headless Chrome through Puppeteer. It's a Node.js package, and yes, that means we need Node alongside PHP. Sounds like overkill, but you'll see it's not that bad. First things first -- we need Node.js. Puppeteer won't run without it. If you're on macOS with Homebrew, one command: On Ubuntu it's a bit longer: Quick check that everything installed: See version numbers? Good, moving on. Now we need a separate folder for our screenshot tool. I usually put it next to the PHP project, but it doesn't really matter where. This will take a minute. Puppeteer downloads a full Chromium binary during installation -- somewhere between 170 and 400 megabytes depending on your OS. Yeah, it's a chunky package. That's the price of getting a real browser you can control from code. If you're on Ubuntu, Chrome might fail to launch the first time -- it needs system libraries that aren't there on a fresh install: On macOS you can skip this step -- everything works out of the box. Now for the fun part. Create a file called screenshot.js inside the screenshot-tool folder: What this does: takes a URL from the command line arguments, launches headless Chrome, opens the page, waits for it to load, takes a screenshot, saves it to a file. Then closes the browser. Let's test it right away: If everything worked, you'll see a new test.png file in the folder. Open it -- if you see a screenshot of the page, the script works. The script works from the terminal. Now we need PHP to call it. The idea is simple: PHP runs our Node script through shell_exec() like any other console command, and picks up the resulting PNG from disk. Note the 2>&1 at the end of the command -- it redirects stderr to stdout. Without it, if Node throws an error, PHP simply won't see it and you'll be left guessing why nothing works. The basic version works, but let's make it more useful. Here's a wrapper function with viewport size and full-page support: Now you can take desktop, mobile, and full-page screenshots with one function call. We went through the whole thing: Node.js, Puppeteer with Chromium, a JS script, shell_exec from PHP, a wrapper with options. It works -- but it's a lot of moving parts for one task. You can get the same result with a single HTTP request. No Node.js, no Chromium, no two scripts in two languages: I built ScreenshotRun as a simpler alternative — one HTTP request instead of managing Puppeteer. Free tier gives you 300 screenshots/month. If you're using AI coding tools like Claude or Cursor, there's also an MCP server that lets your AI agent take screenshots directly. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 500;">brew -weight: 500;">install node -weight: 500;">brew -weight: 500;">install node -weight: 500;">brew -weight: 500;">install node -weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash - -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y nodejs -weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash - -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y nodejs -weight: 500;">curl -fsSL https://deb.nodesource.com/setup_20.x | -weight: 600;">sudo -E bash - -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y nodejs node -v -weight: 500;">npm -v node -v -weight: 500;">npm -v node -v -weight: 500;">npm -v mkdir screenshot-tool cd screenshot-tool -weight: 500;">npm init -y -weight: 500;">npm -weight: 500;">install puppeteer mkdir screenshot-tool cd screenshot-tool -weight: 500;">npm init -y -weight: 500;">npm -weight: 500;">install puppeteer mkdir screenshot-tool cd screenshot-tool -weight: 500;">npm init -y -weight: 500;">npm -weight: 500;">install puppeteer -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 \ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 \ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 -weight: 600;">sudo -weight: 500;">apt-get -weight: 500;">install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 \ libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \ libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2 const puppeteer = require('puppeteer'); const url = process.argv[2]; const output = process.argv[3] || 'screenshot.png'; if (!url) { console.error('Usage: node screenshot.js <url> [output-file]'); process.exit(1); } (async () => { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '---weight: 500;">disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000, }); await page.screenshot({ path: output, fullPage: false }); await browser.close(); console.log(output); })(); const puppeteer = require('puppeteer'); const url = process.argv[2]; const output = process.argv[3] || 'screenshot.png'; if (!url) { console.error('Usage: node screenshot.js <url> [output-file]'); process.exit(1); } (async () => { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '---weight: 500;">disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000, }); await page.screenshot({ path: output, fullPage: false }); await browser.close(); console.log(output); })(); const puppeteer = require('puppeteer'); const url = process.argv[2]; const output = process.argv[3] || 'screenshot.png'; if (!url) { console.error('Usage: node screenshot.js <url> [output-file]'); process.exit(1); } (async () => { const browser = await puppeteer.launch({ headless: 'new', args: ['--no-sandbox', '---weight: 500;">disable-setuid-sandbox'], }); const page = await browser.newPage(); await page.setViewport({ width: 1280, height: 800 }); await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000, }); await page.screenshot({ path: output, fullPage: false }); await browser.close(); console.log(output); })(); node screenshot.js https://screenshotrun.com test.png node screenshot.js https://screenshotrun.com test.png node screenshot.js https://screenshotrun.com test.png <?php $url = 'https://screenshotrun.com'; $outputDir = __DIR__ . '/screenshots'; $filename = md5($url) . '.png'; $outputFile = $outputDir . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile) ); echo "Running: {$command}\n"; $output = shell_exec($command); if (file_exists($outputFile)) { echo "Done! Saved to: {$outputFile}\n"; echo "File size: " . round(filesize($outputFile) / 1024) . " KB\n"; } else { echo "Something went wrong.\n"; echo "Output: {$output}\n"; } <?php $url = 'https://screenshotrun.com'; $outputDir = __DIR__ . '/screenshots'; $filename = md5($url) . '.png'; $outputFile = $outputDir . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile) ); echo "Running: {$command}\n"; $output = shell_exec($command); if (file_exists($outputFile)) { echo "Done! Saved to: {$outputFile}\n"; echo "File size: " . round(filesize($outputFile) / 1024) . " KB\n"; } else { echo "Something went wrong.\n"; echo "Output: {$output}\n"; } <?php $url = 'https://screenshotrun.com'; $outputDir = __DIR__ . '/screenshots'; $filename = md5($url) . '.png'; $outputFile = $outputDir . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile) ); echo "Running: {$command}\n"; $output = shell_exec($command); if (file_exists($outputFile)) { echo "Done! Saved to: {$outputFile}\n"; echo "File size: " . round(filesize($outputFile) / 1024) . " KB\n"; } else { echo "Something went wrong.\n"; echo "Output: {$output}\n"; } <?php function takeScreenshot( string $url, string $outputDir, int $width = 1280, int $height = 800, bool $fullPage = false ): ?string { $filename = md5($url . $width . $height . ($fullPage ? '1' : '0')) . '.png'; $outputFile = rtrim($outputDir, '/') . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s %d %d %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile), $width, $height, $fullPage ? 'true' : 'false' ); shell_exec($command); return file_exists($outputFile) ? $outputFile : null; } // Desktop screenshot $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots'); echo $file ? "Desktop: {$file}\n" : "Failed\n"; // Mobile (iPhone-sized) $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 375, 812); echo $file ? "Mobile: {$file}\n" : "Failed\n"; // Full page with scrolling $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 1280, 800, true); echo $file ? "Full page: {$file}\n" : "Failed\n"; <?php function takeScreenshot( string $url, string $outputDir, int $width = 1280, int $height = 800, bool $fullPage = false ): ?string { $filename = md5($url . $width . $height . ($fullPage ? '1' : '0')) . '.png'; $outputFile = rtrim($outputDir, '/') . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s %d %d %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile), $width, $height, $fullPage ? 'true' : 'false' ); shell_exec($command); return file_exists($outputFile) ? $outputFile : null; } // Desktop screenshot $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots'); echo $file ? "Desktop: {$file}\n" : "Failed\n"; // Mobile (iPhone-sized) $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 375, 812); echo $file ? "Mobile: {$file}\n" : "Failed\n"; // Full page with scrolling $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 1280, 800, true); echo $file ? "Full page: {$file}\n" : "Failed\n"; <?php function takeScreenshot( string $url, string $outputDir, int $width = 1280, int $height = 800, bool $fullPage = false ): ?string { $filename = md5($url . $width . $height . ($fullPage ? '1' : '0')) . '.png'; $outputFile = rtrim($outputDir, '/') . '/' . $filename; $scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js'; $command = sprintf( 'node %s %s %s %d %d %s 2>&1', escapeshellarg($scriptPath), escapeshellarg($url), escapeshellarg($outputFile), $width, $height, $fullPage ? 'true' : 'false' ); shell_exec($command); return file_exists($outputFile) ? $outputFile : null; } // Desktop screenshot $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots'); echo $file ? "Desktop: {$file}\n" : "Failed\n"; // Mobile (iPhone-sized) $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 375, 812); echo $file ? "Mobile: {$file}\n" : "Failed\n"; // Full page with scrolling $file = takeScreenshot('https://screenshotrun.com', __DIR__ . '/screenshots', 1280, 800, true); echo $file ? "Full page: {$file}\n" : "Failed\n"; <?php $apiKey = 'YOUR_API_KEY'; $ch = curl_init('https://screenshotrun.com/api/v1/screenshots'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'url' => 'https://screenshotrun.com', 'format' => 'png', 'block_cookies' => true, ]), CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Screenshot ID: " . $response['data']['id']; <?php $apiKey = 'YOUR_API_KEY'; $ch = curl_init('https://screenshotrun.com/api/v1/screenshots'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'url' => 'https://screenshotrun.com', 'format' => 'png', 'block_cookies' => true, ]), CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Screenshot ID: " . $response['data']['id']; <?php $apiKey = 'YOUR_API_KEY'; $ch = curl_init('https://screenshotrun.com/api/v1/screenshots'); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ 'Authorization: Bearer ' . $apiKey, 'Content-Type: application/json', ], CURLOPT_POSTFIELDS => json_encode([ 'url' => 'https://screenshotrun.com', 'format' => 'png', 'block_cookies' => true, ]), CURLOPT_RETURNTRANSFER => true, ]); $response = json_decode(curl_exec($ch), true); curl_close($ch); echo "Screenshot ID: " . $response['data']['id'];