Features Pricing Docs Blog Playground Log In Sign Up
Back to Blog

How to take a website screenshot with PHP

How to capture website screenshots from PHP using Puppeteer and Node.js -- from installing dependencies to a working PNG. Plus a faster way with an API.

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.

Let's try it.

Installing Node.js

First things first -- we need Node.js. Puppeteer won't run without it.

If you're on macOS with Homebrew, one command:

brew install node

On Ubuntu it's a bit longer:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

Quick check that everything installed:

node -v
npm -v

See version numbers?

node js version

Good, moving on.

Creating the project and installing Puppeteer

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.

mkdir screenshot-tool
cd screenshot-tool
npm init -y
npm install puppeteer

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:

sudo apt-get install -y libnss3 libatk1.0-0 libatk-bridge2.0-0 \
    libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
    libxrandr2 libgbm1 libpango-1.0-0 libcairo2 libasound2

On macOS you can skip this step -- everything works out of the box.

Writing the screenshot script

Now for the fun part. Create a file called screenshot.js inside the screenshot-tool folder:

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', '--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);
})();

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:

node screenshot.js https://screenshotrun.com test.png

If everything worked, you'll see a new test.png file in the folder:

create test screenshot

Open test.png. If you see a screenshot of screenshotrun.com, the script works.

result created schreenshot with php

Try it with other sites too -- github.com, your own project, whatever you like.

Connecting it to PHP

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.

First, create a folder where screenshots will go. You can put it right inside your PHP project:

mkdir -p /path/to/your/php-project/screenshots

Now create a PHP file. I'll call it take-screenshot.php:

<?php

$url = 'https://screenshotrun.com';
$outputDir = __DIR__ . '/screenshots';
$filename = md5($url) . '.png';
$outputFile = $outputDir . '/' . $filename;

// Path to our Node script (use your own path)
$scriptPath = __DIR__ . '/../screenshot-tool/screenshot.js';

// Build the command
$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";
}

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.

Run it:

php take-screenshot.php

If everything went well, you should see something like this:

done php screenshot

Go to the screenshots folder and open the file. If you see the screenshotrun.com homepage, you've got a working screenshot tool in PHP.

new screenshot done

Adding options

The basic version works, but let's make it a bit more useful. We'll add viewport size and full-page capture support.

Updated screenshot.js:

const puppeteer = require('puppeteer');

const args = process.argv.slice(2);
const url = args[0];
const output = args[1] || 'screenshot.png';
const width = parseInt(args[2]) || 1280;
const height = parseInt(args[3]) || 800;
const fullPage = args[4] === 'true';

if (!url) {
    console.error('Usage: node screenshot.js <url> [output] [width] [height] [fullPage]');
    process.exit(1);
}

(async () => {
    const browser = await puppeteer.launch({
        headless: 'new',
        args: ['--no-sandbox', '--disable-setuid-sandbox'],
    });

    const page = await browser.newPage();
    await page.setViewport({ width, height });
    await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
    await page.screenshot({ path: output, fullPage });
    await browser.close();

    console.log(JSON.stringify({ file: output, width, height, fullPage }));
})();

And a PHP wrapper function so you don't copy-paste the same code every time:

<?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";

Run it:

php take-screenshot.php

settings-mob-desctop

Now you can take desktop, mobile, and full-page screenshots with one function call. Run it and compare the files -- the difference between desktop and mobile versions is pretty obvious.

mobile screenshot version

There's a simpler way

We went through the whole thing: installed Node.js, downloaded Puppeteer with Chromium, wrote a JS script, hooked it up to PHP via shell_exec, built a wrapper with options. Does it work? It does.

But step back for a second. We pulled in an entire browser and a second language for one task: getting an image of a URL.

You can get the same result with a single HTTP request. No Node.js, no Chromium, no two scripts in two languages.

Here's what it looks like with the ScreenshotRun API:

<?php

$apiKey = 'YOUR_API_KEY'; // Get one free: screenshotrun.com/register

$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'];

One file. One language. The only dependency is cURL, which PHP ships with by default.

The screenshot gets created on the API side -- the browser, fonts, everything is already handled there. A few seconds later you can download the image by its ID.

The free plan gives you 300 screenshots per month, no credit card. Enough to try it and see if it fits.

More from the blog

View all posts

How to Generate Open Graph Images with a Screenshot API

Learn how to automatically generate OG images from HTML templates using a screenshot API. Includes code examples for JavaScript, Laravel, and curl, plus tips on caching, storage, and debugging.

Read more →

Best Screenshot API in 2026: Honest Comparison for Developers

Not all screenshot APIs are built the same. We compared the most popular options on the market — pricing, webhooks, rendering engines, free tiers, and real cost per screenshot — so you can pick the one that actually fits your project without overpaying for features you don't need.

Read more →