Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use PhantomJS to capture a JavaScript-heavy page by opening the URL, checking the page.open status, waiting for the page’s own asynchronous content to become ready, and then calling page.render before phantom.exit(). PhantomJS runs page JavaScript by default, but its load callback only indicates that the document load completed—not that a modern single-page application has finished fetching and painting every component. Treat PhantomJS as a legacy option: development is suspended, and the official repository has been archived read-only since May 30, 2023.

What PhantomJS can—and cannot—capture

PhantomJS is a command-line, headless browser. Its documented capture flow is intentionally small: create a WebPage object, set the viewport, call page.open(url, callback), render with page.render(filename), and terminate with phantom.exit(). The official Quick Start shows the same sequence.

JavaScript is enabled by default according to the settings reference. That lets PhantomJS execute scripts, draw Canvas and SVG, and load images. However, page.open invokes its callback when the page load process finishes. Applications that fetch data after that point can still be incomplete when you render. There is no documented, universal “all application work is finished” event for every framework, so readiness must be decided for the target site.

Compatibility is the main qualification. The project homepage states, “Important: PhantomJS development is suspended until further notice.” The official GitHub repository identifies 2.1 as the latest stable release and is archived read-only (archive date: May 30, 2023). Current browser APIs, fonts, security policies, bot checks, and JavaScript bundles may therefore behave differently—or fail entirely. Verify the exact pages you need, and choose a maintained browser automation stack when current web-platform compatibility is a requirement.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Install and run a minimal capture

1. Put PhantomJS on your PATH

Install the PhantomJS executable for your operating system, then confirm that the command is available:

phantomjs --version

The Quick Start runs a script with phantomjs hello.js. Keep the executable and script in a controlled build environment if captures must be reproducible; PhantomJS itself is no longer receiving development updates.

2. Create the capture script

var page = require('webpage').create();
page.viewportSize = { width: 1280, height: 900 };

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    console.log('Failed to load the page');
    phantom.exit(1);
    return;
  }

  // Add a site-specific readiness test or a deliberate delay here
  // when content arrives after the initial page load.
  page.render('capture.png');
  phantom.exit();
});

3. Execute it and inspect the output

phantomjs capture.js

A successful run writes capture.png in the current directory and exits with status 0. A failed page.open call prints the message and exits with status 1. Use an absolute output path in scheduled jobs so the destination does not depend on the process’s working directory.

Make asynchronous content ready before rendering

Use a fixed delay only when the page is predictable

The project homepage demonstrates opening a page, waiting with a short timeout, and then capturing. A delay is easy to add, but its duration is an example rather than a universal PhantomJS setting. If the network is slow, the delay can be too short; if the page is fast, it wastes time.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
page.viewportSize = { width: 1280, height: 900 };

page.open('https://example.com/dashboard', function (status) {
  if (status !== 'success') {
    console.log('Open failed: ' + status);
    phantom.exit(1);
    return;
  }

  window.setTimeout(function () {
    page.render('dashboard.png');
    phantom.exit();
  }, 3000); // Tune for this page; it is not a universal readiness value.
});

Prefer a page-specific readiness check

When the site exposes a stable marker—such as a table, a “loaded” class, or a known application state—poll that marker from the PhantomJS script and render only after it appears. This is an implementation approach, not a documented universal readiness API.

var page = require('webpage').create();
page.viewportSize = { width: 1280, height: 900 };

var deadline = Date.now() + 15000;
var timer;

function checkReady() {
  var ready = page.evaluate(function () {
    return !!document.querySelector('[data-capture-ready]');
  });

  if (ready) {
    window.clearInterval(timer);
    page.render('ready.png');
    phantom.exit(0);
  } else if (Date.now() > deadline) {
    window.clearInterval(timer);
    console.log('Readiness marker did not appear before timeout');
    phantom.exit(2);
  }
}

page.open('https://example.com/app', function (status) {
  if (status !== 'success') {
    console.log('Open failed: ' + status);
    phantom.exit(1);
    return;
  }
  timer = window.setInterval(checkReady, 250);
});

Choose a marker that represents the content you actually need, not merely the presence of a root element that is rendered before data arrives. If you control the application, adding a capture-only marker is more reliable than guessing a delay.

Understand the difference between resource timeout and readiness

The WebPage settings API includes a resource timeout. It stops an individual requested resource after the configured number of milliseconds; it does not wait for application content to finish rendering. Set it to prevent a stuck request from holding a job forever, but still use a readiness condition or bounded delay for asynchronous UI work.

Configure the page before opening the URL

The settings reference says page settings apply during the initial page.open call, so set them before opening. Common controls include JavaScript, image loading, user agent, resource timeout, and web security.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
page.settings = {
  javascriptEnabled: true,
  loadImages: true,
  userAgent: 'Mozilla/5.0 (compatible; PhantomJS capture)',
  resourceTimeout: 20000
};
page.viewportSize = { width: 1440, height: 900 };

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    phantom.exit(1);
    return;
  }
  page.render('configured.png');
  phantom.exit();
});

JavaScript and image loading are enabled by default, but setting them explicitly documents your intent. A custom user agent can alter server responses; use one only when you understand the site’s behavior. Do not disable web security or ignore TLS problems as a routine screenshot fix. Those switches can hide real production failures and reduce isolation.

Choose viewport, clipping, and output format

Viewport size controls layout

Set page.viewportSize to the CSS viewport you want the site to see. Responsive breakpoints, navigation, and typography can change when width or height changes.

page.viewportSize = { width: 1366, height: 768 };

The viewport is not automatically the full page. A long page may require the screen-capture techniques described in the official screen capture guide, and results remain subject to the legacy rendering engine’s handling of current CSS and web fonts.

Clip a defined rectangle

Use page.clipRect when the artifact should contain only a known region:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.clipRect = { top: 120, left: 40, width: 900, height: 500 };
page.render('content-area.png');

Coordinates are pixels in the rendered page. A clip rectangle is useful for a chart, a report panel, or a visual-regression region; omit it when you need the normal page capture.

Let the filename select the format

page.render derives the output format from the filename extension. The API documents PDF, PNG, JPEG, BMP, and PPM; GIF support depends on the Qt build. PNG is lossless and generally suited to UI or text. JPEG can be smaller for photographic content and supports quality options. PDF is a document output rather than a pixel-for-pixel browser screenshot.

page.render('page.png');
page.render('page.jpg', { quality: 85 });
page.render('page.pdf');

PNG compression options and JPEG quality are documented by the page.render API. Treat these as encoding choices, not a way to fix missing content or browser incompatibility.

Full-page capture versus a clip

Goal Configuration Trade-off
Responsive viewport screenshot Set page.viewportSize; leave clipRect unset Shows the layout at that viewport; long pages may not fit in one visible frame.
Specific component Set page.clipRect to the component’s rectangle Stable, focused artifact, but coordinates must match the page state.
Printable document Render to .pdf and configure viewport or PDF-related options documented by the API Useful for documents; pagination and modern CSS support are limited by PhantomJS’s old engine.

Troubleshoot failed or incomplete captures

“Failed to load the page” or a non-success status

  • Confirm the URL is reachable from the machine running PhantomJS and includes the correct scheme.
  • Inspect the page’s network and TLS requirements. A certificate, redirect, DNS, or blocked resource can prevent a successful open.
  • Do not immediately turn off web security or TLS checks; first correct the site or environment problem.
  • Use resourceTimeout to bound a hanging resource, remembering that it does not signal application readiness.

The screenshot is blank or missing data

  • Check that JavaScript and images are enabled before page.open.
  • Increase a bounded delay or, preferably, wait for a marker that represents the loaded data.
  • Log the result of page.evaluate for the marker and capture only after it is present.
  • Test the exact page: a current framework, unsupported browser API, bot check, or authentication flow may not work in PhantomJS.

The layout is the wrong size

  • Set page.viewportSize before opening the URL.
  • Check responsive breakpoints at the selected width and height.
  • Use page.clipRect only after confirming the target coordinates in that viewport.

The file format or quality is unexpected

  • Check the output extension; PhantomJS chooses the rendering format from it.
  • Use PNG for lossless interface text, JPEG quality for photographic output, and PDF when a document is the intended artifact.
  • Remember that GIF availability depends on the Qt build.

The capture works on one site but not another

That is expected for a suspended, archived browser engine. Compare the failing page’s JavaScript APIs, security headers, fonts, authentication, and asynchronous behavior rather than assuming a single global delay will fix it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Reliability and operational guidance

Make each job deterministic: set the viewport and settings before opening, use a bounded readiness strategy, write to an explicit path, check the callback status, and return a nonzero exit code on failure. Record the target URL, viewport, output format, and readiness condition alongside the artifact so later differences are explainable.

A fixed delay has predictable code but uncertain completion time. A page-specific marker can reduce unnecessary waiting and avoid capturing partial content, but it requires cooperation from each application. For recurring jobs, fail loudly when the marker deadline expires instead of silently publishing an incomplete image.

Because there is no current PhantomJS support plan, validate representative pages whenever a site changes its frontend. If the requirement is modern browser fidelity rather than preserving an existing PhantomJS script, plan a migration to a maintained browser automation tool; no particular alternative is established by the PhantomJS documentation cited here.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server when you need a current, one-request capture instead of maintaining a PhantomJS runtime. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before the shot; each step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

One GET request returns PNG, JPEG, WebP, or PDF:

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

See the ScreenshotNeo API documentation for options such as full-page capture, CSS-selector elements, dark mode, device presets, retina scale, PDF paper and page ranges, custom JavaScript and CSS, click-before-capture actions, selector hiding, network-idle waits, request blocking, headers, cookies, user agents, timezone and geolocation, transparent backgrounds, resizing, TTL caching, signed links, asynchronous webhooks, bulk capture, usage data, and the OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which can simplify a switch.

Python

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.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://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients, so AI agents can request captures directly. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

FAQ

Does PhantomJS wait for AJAX requests automatically?

No. Its open callback marks page-load completion, not completion of every later application request. Add a target-specific readiness check or bounded delay.

Can I capture a PDF instead of an image?

Yes. Render to a filename ending in .pdf, while remembering that pagination and modern CSS behavior come from the legacy rendering engine.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is PhantomJS still maintained?

No. The project says development is suspended, and its repository was archived read-only on May 30, 2023; 2.1 is identified as the latest stable release.

Frequently Asked Questions

What exit code should a failed PhantomJS capture use?

Call phantom.exit(1) (or another nonzero code) after a failed page.open status or expired readiness deadline so schedulers can detect the failure.

Should I use a delay or a readiness marker?

Use a page-specific marker when you can define one; use a bounded delay only when the page’s update timing is predictable and you accept the risk of waiting too little or too long.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.