ScreenshotNeo

BlogScreenshots on your device

How to Capture a Server Desktop Screenshot with PHP

Learn the difference between desktop and web-page screenshots, then capture either one reliably with PHP, Chromium, Playwright, or ScreenshotNeo.

By the ScreenshotNeo team30 September 20264 min read

How to Capture a Server Desktop Screenshot with PHP

Direct answer: first decide whether you need an image of the operating system’s current desktop or an image of a web page rendered by a browser on the server. PHP’s native imagegrabscreen() function captures the whole visible screen, but it is available only on Windows. For a URL rendered on a server, use browser automation such as Playwright for PHP or chrome-php/chrome. A headless browser does not capture a physical monitor; it renders the page in Chromium and saves the result.

This guide covers both meanings, with runnable PHP examples, full-page and element captures, deployment concerns, troubleshooting, and a hosted option when you do not want to install or operate a browser.

1. Define what “server desktop” means

There are two separate jobs:

Requirement Correct approach What it captures
Capture the current Windows desktop PHP GD imagegrabscreen() The screen visible to the PHP process, including windows and the taskbar
Capture a web page at a URL Playwright PHP or chrome-php/chrome A browser-rendered viewport, full page, or selected element
Capture an existing interactive Linux desktop session A display or desktop-capture strategy A session attached to a display; this is separate from headless page rendering

Do not call a browser screenshot a capture of the server’s physical monitor. Headless Chromium can render a page without any visible browser window. If you need the exact desktop that a logged-in operator sees, confirm which account owns the display session and whether the PHP process can access it.

2. Capture the Windows screen with PHP GD

The PHP manual documents imagegrabscreen() as a whole-screen capture function and marks it Windows-only. It returns a GdImage object on success in modern PHP versions and false on failure. The GD extension must be enabled. See the PHP manual before deploying.

<?php
declare(strict_types=1);

if (!function_exists('imagegrabscreen')) {
    throw new RuntimeException('imagegrabscreen() is unavailable. Check that PHP is running on Windows with GD enabled.');
}

$screen = imagegrabscreen();
if ($screen === false) {
    throw new RuntimeException('The desktop could not be captured by the PHP process.');
}

$output = __DIR__ . DIRECTORY_SEPARATOR . 'server-desktop.png';
if (!imagepng($screen, $output)) {
    throw new RuntimeException('Could not write ' . $output);
}

imagedestroy($screen);
echo "Saved {$output}\n";

Important limitations

  • This is not a cross-platform server screenshot API. The documented function is available only on Windows.
  • It captures the screen available to the PHP process. A Windows service running without an interactive session may return an unusable result or fail.
  • It captures everything visible, so credentials, notifications, and other windows can enter the file. Restrict access to the output and delete it when it is no longer needed.
  • For a website screenshot, this approach is the wrong abstraction: there may be no logged-in desktop at all.

3. Capture a web page with Playwright PHP

For a server-side page image, launch Chromium, open a page, wait for the state that matters, and save a screenshot. The Playwright PHP project documents this workflow and currently lists PHP 8.2 or newer and Node.js 20 or newer as requirements; verify the project’s current installation instructions for your environment. The project documentation is available from the Playwright PHP repository.

After installing the package and the required browser binaries according to that project’s instructions, this is the smallest useful script:

<?php
require __DIR__ . '/vendor/autoload.php';

use Playwright\Playwright;

$playwright = Playwright::chromium(['headless' => true]);
$page = $playwright->newPage();
$page->goto('https://example.com');
$page->screenshot(__DIR__ . '/example.png');
$playwright->close();

A navigation call only tells you that navigation reached its configured point. It does not prove that an application finished rendering data, fonts, charts, or images. Make the desired state explicit:

<?php
require __DIR__ . '/vendor/autoload.php';

use Playwright\Playwright;

$browser = Playwright::chromium(['headless' => true]);
$page = $browser->newPage([
    'viewport' => ['width' => 1440, 'height' => 900],
    'deviceScaleFactor' => 1,
]);

try {
    $page->goto('https://example.com/dashboard', ['waitUntil' => 'networkidle']);
    $page->locator('h1')->waitFor(['state' => 'visible', 'timeout' => 15000]);
    $page->screenshot(__DIR__ . '/dashboard.png', [
        'fullPage' => true,
        'animations' => 'disabled',
    ]);
} finally {
    $browser->close();
}

Use the narrowest screenshot scope that answers the question:

  • Viewport: the pixels currently visible in the browser window. Use this for a “what did the user see?” artifact.
  • Full page: the scrollable document, including content below the fold. Use it for a complete article or landing page, but avoid it for extremely long documents unless you need the entire image.
  • Element: one component such as a card, chart, or navigation bar. This reduces file size and avoids unrelated content.

Element capture

<?php
require __DIR__ . '/vendor/autoload.php';

use Playwright\Playwright;

$browser = Playwright::chromium(['headless' => true]);
$page = $browser->newPage(['viewport' => ['width' => 1365, 'height' => 768]]);

try {
    $page->goto('https://example.com/pricing', ['waitUntil' => 'domcontentloaded']);
    $card = $page->locator('[data-testid="pricing-card"]');
    $card->waitFor(['state' => 'visible', 'timeout' => 10000]);
    $card->screenshot(__DIR__ . '/pricing-card.png');
} finally {
    $browser->close();
}

Make captures deterministic

For visual comparisons, control the viewport, device scale factor, browser version, fonts, data, timezone, and animation state. Disable or freeze transitions where possible. Wait for the application’s own ready marker instead of relying only on a fixed sleep. A screenshot records exactly what the page looked like at that moment, so a loading skeleton or late chart can become part of the artifact.

4. Full-page details, lazy content, and dynamic applications

Full-page capture can expose content that is loaded only after scrolling. If the site uses lazy images, scroll through the document before capturing or use a capture option that loads lazy content. A practical pattern is to evaluate a small scrolling loop, then wait for images:

<?php
// Add this after navigation and before screenshot().
$page->evaluate(<<<'JS'
async () => {
  await new Promise(resolve => {
    let y = 0;
    const step = 600;
    const timer = setInterval(() => {
      window.scrollBy(0, step);
      y += step;
      if (y >= document.body.scrollHeight) {
        clearInterval(timer);
        window.scrollTo(0, 0);
        resolve();
      }
    }, 100);
  });
}
JS
);
$page->waitForTimeout(500);
$page->screenshot(__DIR__ . '/long-page.png', ['fullPage' => true]);

Use a selector wait for dashboards and single-page applications. For pages that never become network-idle because of analytics or WebSockets, prefer domcontentloaded plus a specific ready selector. Avoid unbounded waits; set a navigation timeout and record the URL and failure reason in your job logs.

5. Alternative: chrome-php/chrome

chrome-php/chrome is another PHP interface for controlling Chrome or Chromium. Its documentation covers opening pages, saving screenshots, clip-based captures, and full-page capture. Review the project’s current API and installation guidance at github.com/chrome-php/chrome.

<?php
require __DIR__ . '/vendor/autoload.php';

use HeadlessChromium\BrowserFactory;

$factory = new BrowserFactory();
$browser = $factory->createBrowser(['headless' => true]);

try {
    $page = $browser->createPage();
    $page->navigate('https://example.com')->waitForNavigation();
    $page->screenshot()->saveToFile(__DIR__ . '/example.png');
} finally {
    $browser->close();
}

Choose between libraries using the constraints that matter to your deployment: PHP and browser versions, browser installation, operating-system packages, headless or headed operation, full-page and element support, and the project’s current maintenance guidance. The available project material does not establish a universal performance winner.

6. Headless servers, headed debugging, and Xvfb

Headless mode is normally the simplest shape for automated web-page screenshots because it does not require a visible display. For interactive debugging, a headed browser can help you inspect the page. Playwright’s inspector documentation discusses Xvfb for headed Linux or CI environments without a display. That does not make Xvfb a requirement for headless capture.

Do not copy a generic list of browser flags or operating-system packages into production without checking the browser build, distribution, user permissions, and library documentation. A sandbox or shared-hosting restriction can prevent Chromium from starting even when PHP code is correct.

7. Security and privacy checklist

  • Use a dedicated service account with only the permissions needed to run the browser and write an output directory.
  • Never put API keys, session cookies, Authorization headers, or personal data in a public screenshot path.
  • Use an allowlist when user input controls the target URL; otherwise your capture service can become a server-side request forgery proxy.
  • Set output size limits and timeouts. A page can be very long, resource-heavy, or intentionally hostile.
  • Sanitize filenames and store captures outside the web root unless public access is deliberate.
  • Delete temporary browser profiles and screenshots according to your retention policy.

8. Performance, reliability, and cost considerations

Launching a browser for every request adds startup latency and memory use. A worker process that reuses a browser while creating isolated pages can reduce startup overhead, but recycle workers periodically to limit leaks and stale state. Keep concurrency below the memory capacity of the host. Measure your own pages; the supplied sources do not provide a general benchmark.

Reliability improves when you use explicit readiness checks, bounded navigation and selector timeouts, retries only for transient failures, and structured logs containing the URL, browser version, viewport, duration, and exception. Do not retry deterministic failures such as a missing selector without changing the input.

Self-hosting costs include compute, browser installation, patching, storage, and engineering time. Full-page images and repeated captures increase CPU, memory, bandwidth, and disk use. Cache artifacts when the source page and capture parameters are unchanged.

9. Troubleshooting common errors

Symptom Likely cause Fix
imagegrabscreen() is undefined Non-Windows host or GD is unavailable Use browser automation for a page, or enable GD on a supported Windows runtime.
Blank or partially loaded image Capture ran before the application rendered Wait for a ready selector, required images, or a short application-specific delay.
Navigation timeout Slow origin, blocked resource, redirect loop, or page that never settles Set a bounded timeout, use domcontentloaded, inspect redirects, and wait for a concrete selector.
Chromium will not start in CI Missing browser binary, OS dependency, permission, or sandbox restriction Follow the selected library’s installation guide, verify the browser path, and inspect process logs.
Element screenshot fails Selector is wrong, hidden, detached, or outside the expected frame Use a stable test identifier, wait for visibility, and handle iframes explicitly.
Fonts or layout differ from local Different fonts, viewport, scale factor, browser, or timezone Pin the rendering environment and install the same fonts.
Full-page image is huge Very long document or high device scale factor Capture an element or viewport, reduce scale, split the page, or resize after capture.
Desktop image shows a locked screen PHP runs without access to the interactive Windows session Use page rendering for URLs, or redesign the desktop capture process around an accessible display session.

10. Or skip the browser setup

If your actual goal is a clean screenshot of a URL, ScreenshotNeo provides a single GET request and returns PNG, JPEG, WebP, or PDF. It accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for the current request options. The basic PHP-compatible cURL call is:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

Python:

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)

Node.js:

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(`Screenshot failed: ${res.status}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also supports full-page capture with lazy images loaded, CSS element capture, dark mode, device presets and custom viewports, retina scale, PDF paper settings, custom CSS and JavaScript, clicks, 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. An MCP server provides take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

There are 1,000 free screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots; yearly billing gives two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.

11. FAQ

Can PHP capture a Linux server’s physical desktop?

The supplied PHP function is documented for Windows only. On Linux, determine whether you need a browser-rendered page or an existing display session; they require different tools.

Is headless mode the same as taking a monitor photo?

No. Headless mode renders a web page in a browser process without showing a physical window.

Which screenshot scope should I choose?

Use viewport for visible content, full page for the complete document, and element for one component or region.

Do I need Xvfb for Playwright PHP?

Not for ordinary headless page capture. It is relevant when running a headed browser in a Linux or CI environment without a display.

How can I avoid capturing secrets?

Use a dedicated account, restrict target URLs, avoid public output paths, hide sensitive selectors, and delete files according to your retention policy.