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

Pass headers as one JSON command-line argument, parse that string with system.args, assign the resulting object to page.customHeaders, and only then call page.open. For headers needed only on the first navigation, pass the same object through page.open‘s settings argument instead. PhantomJS command-line arguments are strings, so JSON serialization is the practical way to preserve a header map.

Working command and script

The following script accepts a target URL and a JSON header object. It validates both arguments before making a request and never prints the header values.

headers.js

var system = require('system');
var webpage = require('webpage');
var page = webpage.create();

if (system.args.length < 3) {
  console.log('Usage: phantomjs headers.js <url> <headers-json>');
  phantom.exit(1);
}

var url = system.args[1];
var headers;
try {
  headers = JSON.parse(system.args[2]);
} catch (e) {
  console.log('Invalid headers JSON');
  phantom.exit(1);
}

page.customHeaders = headers;
page.open(url, function (status) {
  console.log('Status: ' + status);
  phantom.exit();
});

Run it from a shell

phantomjs headers.js 'https://example.com/private' '{"Authorization":"Bearer TOKEN","X-Trace":"abc"}'

system.args[0] is the script name. In this invocation, system.args[1] is the URL and system.args[2] is the serialized JSON object. The JSON is parsed into a JavaScript object before assignment. Set page.customHeaders before the first page.open; changing it after navigation has started is too late for that initial request.

Understanding PhantomJS argument positions

PhantomJS uses the form phantomjs [options] somescript.js [arg1 [arg2 [...]]]. The system.args array contains strings, with the script filename first and user-supplied values after it. Consequently, an object cannot be passed as a native JavaScript object from the shell. Serialize it as JSON and call JSON.parse inside the script.

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

Fixed URL, one argument

If the URL is hard-coded, the JSON can be the first user argument:

var headers;
try {
  headers = JSON.parse(system.args[1]);
} catch (e) {
  console.log('Invalid headers JSON');
  phantom.exit(1);
}
page.customHeaders = headers;
page.open('https://example.com/private', function (status) {
  console.log(status);
  phantom.exit();
});

Adjust the argument-count check to match that layout. Keeping the URL as an argument is generally more reusable and makes the usage contract explicit.

Choosing header scope

page.customHeaders: page-wide headers

Assigning page.customHeaders configures additional headers for requests made by the page. Use this when the same authorization, tracing, tenant, or feature header should accompany navigation and subsequent page activity. It is the mechanism shown in the main example.

page.open settings: initial request only

When a header belongs only on the first target request, supply it in the settings object accepted by page.open:

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var settings = {
  operation: 'GET',
  headers: headers
};

page.open(url, settings, function (status) {
  console.log('Status: ' + status);
  phantom.exit();
});

The settings object can also contain other request fields such as encoding and data. This per-request form prevents a header intended for the initial navigation from automatically becoming a page-wide default.

Approach Scope Data shape Best use
page.customHeaders Additional headers for page-issued requests Parsed JSON object Shared authentication or tracing across the page
page.open(url, settings, callback) Initial navigation request Settings object containing headers One-time or navigation-only headers
Separate name/value arguments Requires custom parsing and conventions Multiple strings Only when an integration cannot send JSON

Shell quoting that works

JSON itself uses double quotation marks, so quote the entire JSON argument in a way that prevents the shell from removing those marks.

POSIX shells (Linux, macOS, CI)

phantomjs headers.js 'https://example.com' '{"Authorization":"Bearer TOKEN","X-Trace":"abc"}'

Outer single quotes preserve the JSON double quotes. If a value contains a single quote, construct the argument with a shell-safe escaping strategy or read it from an environment variable.

PowerShell

phantomjs headers.js https://example.com '{"Authorization":"Bearer TOKEN","X-Trace":"abc"}'

PowerShell’s quoting rules differ across versions and invocation contexts. If parsing fails, print only a sanitized representation such as the list of header names, not the argument containing credentials.

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.

Environment-variable pattern

phantomjs headers.js "$TARGET_URL" "$HEADERS_JSON"

Environment variables reduce quoting errors in automation, but command-line arguments can still be visible to process-inspection tools on some systems. Treat bearer tokens, cookies, and API keys as secrets: restrict process access, avoid shell history where possible, and never log system.args or the raw JSON.

Validation and safer parsing

Validate the argument count and JSON syntax before assigning headers. A syntactically valid JSON value can still be the wrong type, so reject arrays, strings, and null when a header map is required:

if (system.args.length < 3) {
  console.log('Usage: phantomjs headers.js <url> <headers-json>');
  phantom.exit(1);
}

var headers;
try {
  headers = JSON.parse(system.args[2]);
} catch (e) {
  console.log('Invalid headers JSON');
  phantom.exit(1);
}

if (!headers || Object.prototype.toString.call(headers) !== '[object Object]') {
  console.log('Headers must be a JSON object');
  phantom.exit(1);
}

page.customHeaders = headers;

Do not include control characters or malformed header names. Header values should be strings that the target server accepts. Keep credentials out of status messages, exception text, screenshots, and CI logs.

Common failures and fixes

“Invalid headers JSON”

  • Cause: The shell stripped JSON quotation marks, or the argument contains a trailing comma.
  • Fix: Quote the complete JSON object, test with a minimal value such as {"X-Test":"1"}, and validate the exact string supplied by the calling process.

The script says the URL or headers argument is missing

  • Cause: The script expects system.args[1] and system.args[2], but only one user argument was supplied.
  • Fix: Use the documented invocation order or change the script’s indexes and usage check together.

The server still returns 401 or 403

  • Cause: The token is expired, the header name/value is wrong, the endpoint requires cookies or additional context, or the server does not authorize PhantomJS’s request.
  • Fix: Confirm the same URL and headers with an independent HTTP client, check token scope and expiry, and inspect the response status without logging secrets. A custom header cannot bypass an authentication policy that requires a cookie, CSRF token, or browser-generated value.

Header works for navigation but not later requests

  • Cause: Headers were supplied in page.open‘s settings, which targets the initial request.
  • Fix: Use page.customHeaders before navigation when the header must accompany page-issued requests.

Header appears to be ignored

  • Cause: page.open was called before assignment, the deployed PhantomJS build behaves differently, or the request is redirected to a host with different requirements.
  • Fix: Move assignment above the first page.open, test the final redirected URL, and verify behavior in the exact PhantomJS build used in production.

Unexpected method or body

  • Cause: The per-request settings object defaults do not match the endpoint’s contract.
  • Fix: Set operation, data, and encoding explicitly when using the page.open overload.

Operational and security considerations

PhantomJS is a legacy runtime

The command-line and API pattern documented for PhantomJS 2.1.1 is a legacy-runtime approach. Pin the executable version in automation, test against the deployed build, and plan a migration if the target site requires modern TLS, JavaScript, or browser features that PhantomJS cannot provide.

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

Redirects, cookies, and browser state

A header added to the initial request does not guarantee that an upstream service will accept it after redirects or that it replaces cookies and CSRF state. Test the complete navigation path. Use the narrowest scope that satisfies the endpoint, especially for authorization headers.

Repeatability

For reliable jobs, make failures visible through exit codes, record status values without credentials, and keep URL, header schema, and PhantomJS version under deployment control. Avoid retrying non-idempotent requests merely because a page load timed out.

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

Or skip the browser setup

If your goal is a clean website screenshot rather than maintaining a PhantomJS script, ScreenshotNeo accepts a URL in one request and supports custom headers through its API options. Its browser workflow can accept cookie or consent banners, remove more than 60 known consent platforms plus newsletter popups and chat widgets, and lets you turn those steps off. Only clean shots are billed; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and each response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all parameters, including custom headers, cookies, user agents, authorization, waits, blocking rules, viewport and device settings, full-page capture, PDF output, signed links, asynchronous jobs, and bulk capture.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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}`);

ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. 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 to try it.

FAQ

Can I pass each header as a separate command-line argument?

Yes, but you must define and maintain your own name/value parsing convention. One JSON object is less ambiguous and preserves the header map in a single argument.

Should authorization headers be page-wide?

Only when every relevant page request requires them. Otherwise, use the page.open settings form so the credential is scoped to the initial navigation.

Why does the example use two indexes for user input?

PhantomJS reserves system.args[0] for the script name, making the URL and JSON values indexes 1 and 2.

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

Frequently Asked Questions

Can I pass each header as a separate command-line argument?

Yes, but you must define and maintain your own name/value parsing convention. One JSON object is less ambiguous and preserves the header map in a single argument.

Should authorization headers be page-wide?

Only when every relevant page request requires them. Otherwise, use the page.open settings form so the credential is scoped to the initial navigation.

Why does the example use two indexes for user input?

PhantomJS reserves system.args[0] for the script name, making the URL and JSON values indexes 1 and 2.

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.