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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Pass the headers as one JSON command-line argument, parse that string with JSON.parse(), assign the resulting object to page.customHeaders, and only then call page.open(). For headers needed on the first navigation only, pass the same object in page.open()‘s settings argument instead.

Use one JSON argument for the headers

PhantomJS exposes command-line values through system.args, and every value in that array is a string. A header collection is structured data, so serialize it as JSON in the shell and parse it inside the script. The argument order is:

  1. the script filename at system.args[0];
  2. the target URL at system.args[1];
  3. the JSON header object at system.args[2].

This invocation sends an authorization header and a tracing header:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
phantomjs headers.js https://example.com '{"Authorization":"Bearer TOKEN","X-Trace":"abc"}'

Use single quotes around the JSON in a POSIX shell so its double quotes reach PhantomJS unchanged. In a Windows shell, use the quoting rules of the shell that launches PhantomJS; the important result is still one argument containing valid JSON.

Complete PhantomJS script

Save this as headers.js. It validates the argument count and JSON syntax before changing page settings, and it never prints the supplied credentials.

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: ' + e);
  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;
page.open(url, function (status) {
  console.log('Status: ' + status);
  phantom.exit(status === 'success' ? 0 : 1);
});

Run it with:

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

page.customHeaders is page-wide: it supplies the additional headers for requests made by the page, not merely the first document request. Set it before the first page.open(); assigning it afterward cannot reliably affect a navigation that has already started.

Initial request only: use page.open settings

Sometimes a credential belongs only on the initial target request. In that case, do not make it a page-wide custom header. Parse the argument as before, then provide it in the settings object accepted by page.open:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var system = require('system');
var webpage = require('webpage');
var page = webpage.create();

if (system.args.length < 3) {
  console.log('Usage: phantomjs initial.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: ' + e);
  phantom.exit(1);
}

var settings = {
  operation: 'GET',
  encoding: 'utf8',
  headers: headers
};

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

The settings form is appropriate when the header should apply to that navigation request only. Choose between the two mechanisms by scope:

Mechanism Scope Best use Data supplied
page.customHeaders Page-wide additional request headers A header that should accompany requests issued by the page One parsed JavaScript object
page.open(url, settings, callback) The request initiated by that page.open call A header needed only for the initial target request Settings object with headers

Pass different header shapes safely

Multiple headers

Represent each header as a property in the JSON object. Header names are strings, and values should normally be strings too:

phantomjs headers.js https://api.example.test '{"Authorization":"Bearer TOKEN","Accept":"application/json","X-Client":"phantom-job-7"}'

Empty or optional headers

An empty object, {}, is valid JSON and results in no additional custom headers. If your wrapper makes headers optional, pass {} explicitly rather than omitting the argument, or change the validation branch to supply an empty object.

Values containing spaces or punctuation

Keep the entire JSON object in one shell argument. Do not split at spaces, commas, or colons. If a token contains a quote, backslash, or newline, escape it according to JSON rules first and then apply the shell’s quoting rules.

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.

Do not put secrets in diagnostics

Never echo the full command, dump system.args, or include the parsed object in error output. Process listings and shell history can also expose command-line arguments, so use a short-lived execution environment or a secret-injection wrapper when your operating system and deployment process provide one. The script above reports parsing failure without printing the submitted JSON.

Argument validation and exit behavior

Check both the number of arguments and the type of parsed value. JSON such as [], null, or a quoted string is syntactically valid but is not a header map. Reject it before assigning page.customHeaders. Returning a nonzero exit code on bad input lets a scheduler or CI job detect configuration errors.

In the callback, PhantomJS reports a status such as success or a failure status. Log only the status and exit with a matching code. A successful HTTP response is not the same as an authenticated application response: your target may still return a 401, 403, login page, or an application-level error. Inspect the page content or title when your job needs that distinction.

Shell portability checklist

  • Confirm that the JSON reaches PhantomJS as one positional argument.
  • Use valid JSON double quotes around property names and string values.
  • Quote the URL separately when it contains shell metacharacters.
  • Do not use a JavaScript object literal with unquoted property names; JSON.parse() requires JSON.
  • Test the exact command under the same shell, user account, and scheduler that will run production jobs.
  • Keep access tokens out of source control, logs, shell history, and shared process listings where possible.

Common failures and fixes

“Usage” appears immediately

Cause: fewer than two supplied values followed the script name, or the URL and headers were not passed as separate arguments.

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

Fix: provide both <url> and <headers-json>. Count arguments in the invoking wrapper, not by adding spaces inside the JSON.

“Invalid headers JSON”

Cause: shell quoting removed quotation marks, a trailing comma was used, or the value was JavaScript syntax rather than JSON.

Fix: run with a minimal object such as {"X-Test":"one"}, then add fields one at a time. Check the command in the actual shell used by the job.

Headers are not present on later requests

Cause: the script used page.open‘s request settings when page-wide behavior was required, or assigned page.customHeaders after navigation began.

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

Fix: assign page.customHeaders before the first page.open(). If only the first request should carry the value, keep the settings-object approach.

The server still returns 401 or 403

Cause: the token may be expired, the header name or value may be wrong, the endpoint may require cookies or another credential, or the server may apply authentication rules beyond the header.

Fix: verify the exact endpoint and token outside PhantomJS, check the response status and page content, and ensure the header is attached to the request scope you intended. Do not solve an authentication problem by printing the secret.

The script works interactively but not in a scheduler

Cause: a different shell changes quoting, environment variables are missing, or the scheduler exposes a different working directory and PhantomJS binary.

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

Fix: use absolute paths, explicitly select the shell, pass a known test header, and capture only non-sensitive status and error messages.

The page loads but scripts or resources behave differently

Cause: custom headers affect requests, while the target may also depend on cookies, user-agent behavior, redirects, or browser features. PhantomJS is a legacy command-line browser and its behavior can differ from modern engines.

Fix: reproduce the request with the same URL, headers, and redirect path; then verify the exact PhantomJS build. The documented command-line pattern targets PhantomJS 2.1.1, so test in the version actually deployed rather than assuming newer-browser behavior.

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

Performance, reliability, and security considerations

Keep the argument small and deterministic

A compact JSON object is easier to quote, audit, and retry than a series of individual name/value arguments. Build it in the calling program when headers are dynamic, serialize once, and pass the resulting string unchanged.

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

Choose the narrowest scope

Page-wide headers are convenient but can unintentionally accompany requests for redirects, scripts, images, or other resources. Use request settings for credentials that belong only to the first navigation. This minimizes accidental disclosure to another origin reached during the page lifecycle.

Retry only the right failures

Retry transient launch, network, or timeout failures according to your job’s policy. Do not blindly retry 401 or 403 responses; those usually indicate invalid credentials or authorization and need correction. Record URL, non-secret status, and timing, but not the header object.

Treat PhantomJS as legacy

The command-line and system.args model is documented for PhantomJS 2.1.1. Pin and test the runtime used by your automation, and plan a migration if the target site requires modern browser features. This technique explains how to pass headers to an existing PhantomJS job; it does not make PhantomJS equivalent to a current browser.

Or skip the browser setup

If your actual goal is a clean website screenshot rather than browser scripting, ScreenshotNeo accepts headers directly through its API and avoids maintaining a PhantomJS process. Its consent step accepts cookie banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and each response identifies the result with X-Page-Verdict and X-Billed headers.

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

Here is the one-call cURL form (replace the URL and key):

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

See the ScreenshotNeo documentation for the header and capture options. The same endpoint can be called from 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)

Or 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. Every plan includes all features; 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

When this pattern is the right choice

  • Use page.customHeaders when the same additional headers must accompany the page’s requests.
  • Use page.open settings when headers belong only to the initial GET.
  • Use JSON rather than separate name/value arguments so the script receives one structured value.
  • Validate before navigation, quote for the real shell, and keep secrets out of logs.
  • Pin and test the legacy PhantomJS runtime, especially when authentication, redirects, or modern site features matter.

Frequently Asked Questions

Can I pass each header as a separate PhantomJS argument?

You can design a parser for repeated name/value arguments, but the reliable pattern here is one JSON object. It preserves the relationship between names and values and avoids ambiguous argument boundaries.

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

Should I use customHeaders for an Authorization token?

Only when that token is intended for the page-wide requests. If it belongs solely to the initial target request, put the parsed object in the settings argument to page.open instead.

What does system.args[0] contain?

It contains the script name. The first value supplied after that is system.args[1], followed by system.args[2] and later positional arguments.

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.