How to Take Website Screenshots with PHP

Published April 3, 2026 · 6 min read · URLSnap Team

Need to capture website screenshots from PHP? Whether you're building a link preview tool, a website monitoring dashboard, or generating thumbnails for a CMS — this guide shows you the fastest way to go from URL to PNG image in PHP.

We'll cover the easy approach (REST API, works on any shared hosting) and the manual approach (headless Chrome via exec), so you can pick what fits your setup.

Option 1: Use URLSnap API (Recommended)

The simplest approach: call the URLSnap REST API using PHP's built-in file_get_contents or cURL. No browser, no extra software — works on shared hosting.

Step 1: Get a Free API Key

Register at urlsnap.dev to get your free API key (20 screenshots/day, no credit card required).

Step 2: Take a Screenshot with cURL (PHP)

<?php

function takeScreenshot(string $url, string $apiKey, string $outputFile = 'screenshot.png'): bool {
    $apiUrl = 'https://urlsnap.dev/api/screenshot?' . http_build_query([
        'url'       => $url,
        'api_key'   => $apiKey,
        'width'     => 1280,
        'height'    => 800,
        'format'    => 'png',
        'full_page' => 'false',
    ]);

    $ch = curl_init($apiUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_TIMEOUT        => 60,
    ]);

    $image = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($httpCode !== 200 || !$image) {
        error_log("Screenshot failed: HTTP $httpCode");
        return false;
    }

    file_put_contents($outputFile, $image);
    return true;
}

// Usage
$apiKey = 'YOUR_API_KEY';
$success = takeScreenshot('https://example.com', $apiKey, '/tmp/shot.png');

if ($success) {
    echo "Screenshot saved!\n";
} else {
    echo "Failed to capture screenshot.\n";
}
Tip: On shared hosting where cURL may be restricted, you can use file_get_contents with a stream context instead — see below.

Using file_get_contents Instead of cURL

<?php

function takeScreenshotSimple(string $url, string $apiKey): ?string {
    $apiUrl = 'https://urlsnap.dev/api/screenshot?' . http_build_query([
        'url'     => $url,
        'api_key' => $apiKey,
        'format'  => 'png',
    ]);

    $context = stream_context_create([
        'http' => [
            'timeout' => 60,
            'ignore_errors' => true,
        ]
    ]);

    $image = @file_get_contents($apiUrl, false, $context);

    // Check HTTP response code
    preg_match('/HTTP\/\d\.\d (\d+)/', $http_response_header[0] ?? '', $m);
    $code = intval($m[1] ?? 0);

    return ($code === 200 && $image) ? $image : null;
}

$apiKey = 'YOUR_API_KEY';
$image = takeScreenshotSimple('https://github.com', $apiKey);
if ($image) {
    file_put_contents('github_shot.png', $image);
    echo "Done! " . strlen($image) . " bytes\n";
}

Full-Page Screenshot

To capture the entire page (scrolled height), add full_page=true:

$apiUrl = 'https://urlsnap.dev/api/screenshot?' . http_build_query([
    'url'       => 'https://example.com',
    'api_key'   => $apiKey,
    'full_page' => 'true',
    'width'     => 1280,
]);

Save Screenshot to a Web-Accessible Path

In a Laravel or WordPress context, save to your public uploads directory:

<?php

// Laravel example
use Illuminate\Support\Facades\Storage;

$image = takeScreenshot('https://example.com', $apiKey);
if ($image) {
    $filename = 'screenshots/' . md5('https://example.com') . '.png';
    Storage::disk('public')->put($filename, $image);
    $url = Storage::url($filename);
    echo "Accessible at: $url\n";
}

// WordPress example
$upload_dir = wp_upload_dir();
$filepath = $upload_dir['path'] . '/screenshot-' . time() . '.png';
file_put_contents($filepath, $image);
$url = $upload_dir['url'] . '/screenshot-' . time() . '.png';

Convert URL to PDF in PHP

URLSnap also supports URL → PDF. Same approach, different endpoint:

<?php

function urlToPdf(string $url, string $apiKey, string $outputFile = 'page.pdf'): bool {
    $apiUrl = 'https://urlsnap.dev/api/pdf?' . http_build_query([
        'url'       => $url,
        'api_key'   => $apiKey,
        'format'    => 'A4',
        'landscape' => 'false',
    ]);

    $ch = curl_init($apiUrl);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 60,
    ]);

    $pdf = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    if ($code !== 200 || !$pdf) return false;
    file_put_contents($outputFile, $pdf);
    return true;
}

urlToPdf('https://urlsnap.dev', $apiKey, '/tmp/page.pdf');
echo "PDF saved!\n";

Handle Rate Limits Gracefully

The free plan allows 20 requests/day. Check the response headers to stay informed:

<?php

$ch = curl_init($apiUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true); // Include headers in output
curl_setopt($ch, CURLOPT_TIMEOUT, 60);

$response = curl_exec($ch);
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$headers = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);

// Parse useful headers
preg_match('/X-Rate-Limit-Remaining: (\d+)/i', $headers, $m);
$remaining = intval($m[1] ?? -1);

preg_match('/X-Rate-Limit-Hint: (.+)/i', $headers, $h);
$hint = trim($h[1] ?? '');

if ($httpCode === 429) {
    echo "Daily limit reached. Upgrade at urlsnap.dev/#pricing\n";
} elseif ($remaining >= 0 && $remaining < 5) {
    echo "Warning: only $remaining free requests left today.\n";
    if ($hint) echo $hint . "\n";
}

Option 2: Headless Chrome via exec() (Self-Hosted)

If you control your server and prefer not to use an external API, you can call Chromium directly from PHP. Note: this requires root/sudo access, significant RAM (~300 MB per tab), and won't work on shared hosting.

<?php

function takeScreenshotLocal(string $url, string $outputFile): bool {
    // Requires Chromium installed: apt install chromium-browser
    $safeUrl = escapeshellarg($url);
    $safeOut = escapeshellarg($outputFile);

    $cmd = "/usr/bin/chromium-browser "
         . "--headless --no-sandbox --disable-gpu "
         . "--screenshot=$safeOut "
         . "--window-size=1280,800 "
         . "$safeUrl 2>/dev/null";

    exec($cmd, $output, $exitCode);
    return $exitCode === 0 && file_exists($outputFile);
}

$ok = takeScreenshotLocal('https://example.com', '/tmp/local-shot.png');
echo $ok ? "Screenshot saved locally.\n" : "Failed.\n";
When to use local vs API: Local Chromium is free but requires server management, uses 200–400 MB RAM per request, and breaks on complex JavaScript. The URLSnap API handles all of that for you and works on any host.

Quick Comparison

URLSnap API — Works on shared hosting, no setup, 20 free req/day, scales instantly.
Local Chromium — Free at scale but needs dedicated server, ops overhead, RAM-heavy.

Ready to take your first screenshot?

Free tier — 20 requests/day, no credit card required.

Get Free API Key →

Frequently Asked Questions

Does it work on shared hosting (cPanel, Bluehost, etc.)?

Yes. The URLSnap API approach uses standard PHP HTTP functions — no shell access or special extensions needed. Local Chromium requires a VPS.

Can I use it inside WordPress?

Absolutely. Use wp_remote_get() or plain cURL inside your plugin/theme. Save the image to the uploads directory and attach it to any post.

Is there a PHP SDK?

Not yet — but the API is simple enough that the code above is all you need. One function, one HTTP call.

What formats are supported?

PNG, JPEG (screenshot), and PDF. All configurable via query parameters.