How to Fix PhantomJS Scripts That Produce No Screenshot from Command-Line Arguments
Fix missing PhantomJS screenshots by validating argument positions, waiting for page.open, checking status, and diagnosing paths, TLS, proxies, and runtime issues.
Direct answer: PhantomJS usually produces no screenshot because the script reads the wrong command-line argument or exits before the asynchronous page.open callback runs. Put PhantomJS options before the script name, put your script arguments after it, read them through require('system').args, render only after a successful page.open, and call phantom.exit() after rendering.
The command shape is:
phantomjs [phantomjs-options] script.js [script-arguments]
For example:
phantomjs capture.js https://example.com output.png
In this command, system.args[1] is the URL and system.args[2] is the output path. The executable name is at index 0.
1. Use a known-good command-line capture script
This diagnostic script validates the argument count, logs the values PhantomJS received, waits for navigation to finish, and renders only when the load status is successful. It follows the argument and callback pattern shown in the official PhantomJS Quick Start.
var system = require('system');
var page = require('webpage').create();
if (system.args.length < 3) {
console.log('Usage: phantomjs capture.js <url> <output.png>');
phantom.exit(1);
}
var address = system.args[1];
var output = system.args[2];
console.log('URL: ' + address);
console.log('Output: ' + output);
page.onError = function (message, trace) {
console.log('Page error: ' + message);
trace.forEach(function (item) {
console.log(' ' + item.file + ':' + item.line + ' - ' + item.function);
});
};
page.open(address, function (status) {
console.log('Load status: ' + status);
if (status === 'success') {
var rendered = page.render(output);
console.log('Render result: ' + rendered);
} else {
console.log('Page did not load; screenshot was not rendered.');
}
phantom.exit();
});
Run it with:
phantomjs capture.js https://example.com output.png
page.render(filename) chooses the output format from the extension. The render API documents PDF, PNG, JPEG, BMP and PPM output; GIF support depends on the Qt build. See the render API.
2. Confirm argument positions and shell quoting
PhantomJS options belong before capture.js. Arguments intended for your script belong after it:
# Correct
phantomjs --debug=true capture.js "https://example.com/a?x=1&y=2" "screenshots/home page.png"
# Incorrect: these values are treated as PhantomJS options or misplaced
phantomjs capture.js --debug=true https://example.com output.png
Print every received argument when diagnosing a complex command:
var system = require('system');
for (var i = 0; i < system.args.length; i++) {
console.log('args[' + i + '] = ' + system.args[i]);
}
Use quotes around URLs containing &, spaces, parentheses or shell metacharacters. Always include the URL protocol, such as https:// or http://. A missing protocol can cause navigation to fail before rendering.
3. Wait for the asynchronous page.open callback
page.open starts navigation and returns before the page is ready. Rendering immediately, or calling phantom.exit() immediately, can terminate PhantomJS before the callback executes.
// Wrong: the process may exit before navigation completes
page.open(address, function (status) {
page.render(output);
});
phantom.exit();
// Correct: render and exit inside the callback
page.open(address, function (status) {
if (status === 'success') {
page.render(output);
}
phantom.exit();
});
Check the callback’s status before rendering. The official screen capture guide and Quick Start use this pattern.
4. Separate navigation failures from file-output failures
Navigation failed
If the log says Load status: fail, inspect the URL, DNS, proxy, TLS and server response before investigating the output file. Add page error logging and, when needed, PhantomJS debug output:
phantomjs --debug=true capture.js https://example.com output.png
The PhantomJS troubleshooting guide recommends checking TLS/OpenSSL configuration when HTTPS behaves differently from HTTP. Do not use --ignore-ssl-errors as a default fix; identify the certificate or TLS problem first.
Navigation succeeded but no file appears
Use an explicit absolute or known-writable path and inspect the process working directory:
pwd
ls -ld .
phantomjs capture.js https://example.com /tmp/example.png
ls -l /tmp/example.png
The Quick Start example writes example.png in the directory from which PhantomJS runs. Relative paths therefore depend on the current working directory, which may differ under cron, a service manager or a build runner. Check directory permissions and whether a file with the same name is being written elsewhere.
5. Check proxies, TLS and the PhantomJS installation
- Print the installed version:
phantomjs --version. - Check that the command resolves to the installation you expect:
which phantomjs(or the platform equivalent). - On Windows, try
--proxy-type=noneif a default proxy causes severe latency, as described in the official troubleshooting guide. - Compare a simple HTTP URL with the failing HTTPS URL to isolate TLS behavior.
- Enable
--debug=trueor remote debugging when page JavaScript or network activity is unclear.
PhantomJS development is suspended, and its GitHub repository is archived and read-only. It remains useful for maintaining an existing script, but evaluate a maintained browser automation tool for new systems. See the official project notice and the archived repository issue.
6. A systematic troubleshooting checklist
| Symptom | Likely cause | Fix |
|---|---|---|
| Usage message or undefined URL | Wrong argument count or index | Log system.args; use URL at index 1 and output at index 2. |
| No callback output | Process exited too early | Move phantom.exit() into the page.open callback. |
Load status: fail |
Bad URL, DNS, TLS, proxy or server failure | Log status, verify protocol, inspect debug/network output and TLS settings. |
| Successful load, missing file | Relative path or permissions | Use an explicit writable path and check the working directory. |
| Blank or incomplete image | Page content loads after navigation callback | Diagnose page timing and JavaScript errors; add only the waiting behavior your page requires. |
| Large delay on Windows | Default proxy | Try --proxy-type=none and verify the network path. |
7. Reliability and performance considerations
- Keep one clear completion path: log status, render on success, then exit.
- Use deterministic output names or unique per-request paths to prevent concurrent runs overwriting each other.
- Capture logs from scheduled jobs; a different working directory is a common reason files appear to be missing.
- Set an external process timeout in the scheduler or wrapper so a stalled navigation cannot run forever.
- Reuse a stable script and validate inputs before starting navigation; malformed arguments otherwise look like browser failures.
- Because PhantomJS is suspended, plan migration risk into long-lived systems and verify compatibility with a maintained browser tool before replacing production capture.
8. Or skip the browser setup
ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP or PDF. Cookie and consent banners are accepted and 60+ known consent platforms, newsletter popups and chat widgets are removed before capture; each step can be turned off. Bot checks, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers identify the page verdict and whether the request was billed. Its MCP server provides take_screenshot, get_page_info and capture_pdf tools for Claude, Cursor and other MCP clients.
See the ScreenshotNeo API documentation for all options.
cURL
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)
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}`);
Relevant capture options include full-page screenshots with lazy images loaded, CSS-selector element capture, dark mode, 12 device presets or any viewport, retina scale, PDF paper size/margins/landscape/page ranges, custom CSS and JavaScript, pre-capture clicks, hidden selectors, waits for selectors/delays/network idle, request and resource blocking, custom headers/cookies/user agent/Authorization, timezone and geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification. Parameter names used by other screenshot APIs also work, easing migration.
The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 screenshots; yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account.
9. FAQ
Why is system.args[0] not my URL?
Index 0 is the script or executable entry. With phantomjs capture.js URL output, the URL is normally system.args[1].
Does page.render wait for page JavaScript?
No. It runs when your code calls it. Put it after navigation completes and account for any page-specific asynchronous content.
Can PhantomJS create PDFs?
Yes. Use a filename with a PDF extension, subject to the formats supported by the installed build.
What should replace PhantomJS for a new project?
Choose a maintained browser automation tool after checking its compatibility with your pages. For an API workflow that avoids browser setup, ScreenshotNeo is an option.
What does an empty output file indicate?
Check the render return value, output path and permissions, then inspect navigation status and page errors. A file-system problem and a failed page load require different fixes.


