How to Use PhantomJS to Screenshot a Page That Requires Login
Log in with PhantomJS, verify the authenticated page, and capture it. Learn the differences between HTTP auth, login forms, and session cookies.
To screenshot a page that requires login with PhantomJS, authenticate first, verify that the page reached a signed-in state, and then call page.render. Use page.settings.userName and password for HTTP authentication; for a regular website login, automate that site’s actual form; or add a valid session cookie. A successful page.open callback only means the page loaded—it does not prove you are logged in.
PhantomJS is a legacy headless browser. Its older WebKit engine may not handle modern identity-provider flows, MFA, or CAPTCHA reliably. Use only accounts and sites you are authorized to access, and do not attempt to bypass login protections.
1. Identify the site’s authentication method
Before writing the script, determine which flow the protected page uses:
| Authentication method | PhantomJS approach | What to verify |
|---|---|---|
| HTTP Basic or similar HTTP authentication | Set page.settings.userName and page.settings.password before the first page.open. |
The protected content appears rather than an HTTP authentication error. |
| HTML login form | Open the login page, fill its real fields, submit it, and wait for the site’s signed-in state. | A known post-login URL or element only shown to signed-in users. |
| Existing session | Load an authorized session cookie with page.addCookie. |
The cookie matches the host and path, has not expired, and the page shows authenticated content. |
These methods are not interchangeable. A username and password in HTTP settings do not fill in an HTML form, and a page-load callback does not indicate whether either method succeeded.
2. Install PhantomJS and create the script
Install a PhantomJS 2.1.1 binary available for your operating system and confirm that the phantomjs executable is on your PATH. The project documentation describes the scriptable browser and its WebPage APIs; check the installer’s provenance and compatibility with your environment before using a legacy binary.
Save the applicable script below as capture.js, then run phantomjs capture.js. Replace example URLs, selectors, and the authenticated-state check with values from your authorized target. Do not put real credentials in source control.
3. Complete runnable example: HTTP authentication
This example is for a server that challenges the request using HTTP authentication. It sets the credentials before opening the protected URL, checks the load status, checks that the response did not leave an HTTP error status, and renders a PNG.
var page = require('webpage').create();
var system = require('system');
page.viewportSize = { width: 1280, height: 900 };
page.settings.userName = system.env.HTTP_USER;
page.settings.password = system.env.HTTP_PASSWORD;
page.settings.resourceTimeout = 30000;
if (!page.settings.userName || !page.settings.password) {
console.log('Set HTTP_USER and HTTP_PASSWORD before running this script.');
phantom.exit(2);
} else {
page.open('https://example.com/protected', function (status) {
if (status !== 'success') {
console.log('Page load failed: ' + status);
phantom.exit(1);
return;
}
if (page.statusCode >= 400) {
console.log('HTTP response status: ' + page.statusCode);
phantom.exit(1);
return;
}
page.render('protected.png');
console.log('Saved protected.png');
phantom.exit(0);
});
}
For example, set HTTP_USER and HTTP_PASSWORD in the process environment using your operating system’s secret-management method. The settings documentation says these values apply to the initial page.open; configure them before opening the page. Do not print the values or commit them.
4. Complete runnable pattern: regular website login form
A regular form requires the site’s actual field selectors, submit behavior, and a site-specific success condition. The following script is runnable against a compatible form after you replace the example URL, selectors, credentials, and signed-in marker. It submits the form and polls for an authenticated-only element before rendering.
var page = require('webpage').create();
var system = require('system');
var attempts = 0;
var maxAttempts = 40;
var pollTimer;
page.viewportSize = { width: 1280, height: 900 };
page.settings.javascriptEnabled = true;
page.settings.loadImages = true;
page.settings.resourceTimeout = 30000;
function finish(code) {
if (pollTimer) {
clearInterval(pollTimer);
pollTimer = null;
}
phantom.exit(code);
}
function hasAuthenticatedMarker() {
return page.evaluate(function () {
return !!document.querySelector('[data-authenticated="true"]');
});
}
function submitLogin() {
// Replace selectors with the real login form selectors for your site.
var submitted = page.evaluate(function (username, password) {
var user = document.querySelector('#username');
var pass = document.querySelector('#password');
var form = document.querySelector('form');
if (!user || !pass || !form) return false;
user.value = username;
pass.value = password;
form.submit();
return true;
}, system.env.SITE_USER, system.env.SITE_PASSWORD);
if (!submitted) {
console.log('Could not find the configured username, password, or form selector.');
finish(1);
return;
}
// A navigation or asynchronous app update may follow form submission.
pollTimer = setInterval(function () {
attempts += 1;
if (hasAuthenticatedMarker()) {
clearInterval(pollTimer);
pollTimer = null;
page.render('authenticated.png');
console.log('Saved authenticated.png');
finish(0);
return;
}
if (attempts >= maxAttempts) {
console.log('Timed out waiting for the authenticated marker. Check credentials, selectors, redirects, and site behavior.');
finish(1);
}
}, 500);
}
if (!system.env.SITE_USER || !system.env.SITE_PASSWORD) {
console.log('Set SITE_USER and SITE_PASSWORD before running this script.');
phantom.exit(2);
} else {
page.open('https://example.com/login', function (status) {
if (status !== 'success') {
console.log('Could not load login page: ' + status);
finish(1);
return;
}
submitLogin();
});
}
The [data-authenticated="true"] selector is a placeholder, not a standard. Prefer a stable element or URL that is only present after authentication. If the site uses a client-side app, adapt the submission and readiness check to its real behavior. Avoid relying on fixed sleeps alone: they can be too short on a slow run and unnecessarily long on a fast one.
PhantomJS runs page code through page.evaluate, but values passed into the page context should be explicit arguments as shown. The script above is a generic form pattern; it has not been tested against a live site, and selectors must match the target.
5. Reuse an authorized session cookie
When you already have a valid session cookie, add it before opening the target page. The cookie domain must match the target host; set the correct path and secure flag for the site. PhantomJS documents page.addCookie for adding cookies and recommends it over assigning the cookie collection directly.
var page = require('webpage').create();
var system = require('system');
page.viewportSize = { width: 1280, height: 900 };
var cookieValue = system.env.SESSION_COOKIE;
if (!cookieValue) {
console.log('Set SESSION_COOKIE before running this script.');
phantom.exit(2);
} else {
var added = page.addCookie({
name: 'session',
value: cookieValue,
domain: 'example.com',
path: '/',
secure: true,
httponly: true
});
if (!added) {
console.log('PhantomJS rejected the cookie. Check its domain and attributes.');
phantom.exit(1);
} else {
page.open('https://example.com/account', function (status) {
if (status !== 'success') {
console.log('Page load failed: ' + status);
phantom.exit(1);
return;
}
var isSignedIn = page.evaluate(function () {
return !!document.querySelector('.account-dashboard');
});
if (!isSignedIn) {
console.log('The session cookie did not produce the expected signed-in page.');
phantom.exit(1);
return;
}
page.render('account.png');
console.log('Saved account.png');
phantom.exit(0);
});
}
}
Replace session, example.com, and .account-dashboard with the site’s real cookie name, host, and authenticated marker. Session cookies are credentials: keep them out of logs, screenshots, source control, and shared files. They may expire or be revoked, and some sites bind sessions to additional state.
6. Set the capture dimensions and output format
page.viewportSizesets the browser viewport dimensions, in CSS pixels.page.clipRectcan limit the rendered capture to a rectangle withtop,left,width, andheight.page.render('file.png')uses the file extension to choose an output format. The documentation lists PNG, JPEG, PDF, BMP, PPM, and GIF support depending on the Qt build.- For JPEG,
page.renderaccepts a quality value from 0 to 100. PNG quality controls compression and does not change the rendered image content.
page.viewportSize = { width: 1440, height: 1000 };
page.clipRect = { top: 0, left: 0, width: 1440, height: 1000 };
page.render('dashboard.jpg', { format: 'jpeg', quality: 85 });
A viewport screenshot may omit content outside the chosen region. Confirm the output dimensions and capture the intended region. The documented APIs provide viewport sizing and clipping; do not assume a full-page capture behavior without checking the rendered result in your setup.
7. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The output is still the login page. | The form selectors or submission behavior are wrong, login failed, or the script captured before redirect or app rendering. | Inspect the real form and its errors; wait for a signed-in-only marker or expected URL before rendering. |
page.open reports success, but access is denied. |
success means the page loaded, not that authentication worked. The server may also have returned an error page. |
Check page.statusCode and an authenticated-state condition; distinguish HTTP authentication from a form login. |
| HTTP authentication keeps prompting or fails. | The server may use a different authentication scheme, or credentials were set after the initial open. | Set userName and password before the first page.open; verify the server’s supported HTTP auth method. |
| The cookie is rejected or has no effect. | Domain/path mismatch, expired or invalid session, wrong cookie name, or site-specific session requirements. | Use an authorized current cookie with matching host and path; verify page.addCookie returns true and check the signed-in marker. |
| The script exits before rendering. | phantom.exit() ran before an asynchronous step completed, or an error path terminated the process. |
Call exit only after the callback or polling condition finishes; clear timers on every exit path. |
| Images or scripts are missing. | Resources timed out, image loading is disabled, or the page depends on browser features unsupported by the old engine. | Keep loadImages enabled when needed, tune resourceTimeout, and inspect resource/network errors where possible. |
| MFA, CAPTCHA, or identity-provider flow fails. | The authentication flow may require modern browser features or a human verification step PhantomJS cannot reliably support. | Use the site’s authorized supported integration or a current browser automation stack. Do not try to bypass the protection. |
| The capture is cropped or unexpectedly sized. | The viewport or clip rectangle does not cover the intended content. | Adjust viewportSize and clipRect, then inspect the output dimensions. |
8. Performance, reliability, and cost
For a single capture, most of the delay usually comes from loading the page, its scripts, and its assets; a fixed delay after login adds time without proving readiness. Prefer a bounded wait for a meaningful authenticated condition and configure resourceTimeout to avoid waiting indefinitely on a resource. If a page’s state changes asynchronously, the readiness check must account for that.
Authentication failures can produce plausible-looking screenshots of a login or error page. Treat the signed-in condition as a required assertion, and fail the job rather than silently saving a misleading image. Protect passwords and session cookies as secrets, limit their lifetime and access, and avoid logging them.
PhantomJS itself has no per-screenshot price in this workflow, but operating it still has infrastructure and maintenance costs. Its legacy engine makes compatibility and dependable operation a concern for modern sites. For supported modern login flows, use an authorized integration or a maintained browser automation option; do not assume PhantomJS can handle MFA or CAPTCHA.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. It can take a screenshot in one GET request; see the API documentation for request options. This is useful when you do not need to automate a protected login flow yourself. Do not send credentials or session cookies unless your use case and the service’s supported options explicitly allow it.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Equivalent Python and Node.js requests:
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)
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 removes cookie banners, popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is on every plan. Sign up for 1,000 free screenshots a month, with no card.
FAQ
Can PhantomJS take a screenshot of a page behind a login?
Yes, if the site’s authentication flow works with PhantomJS and the script verifies the signed-in state before rendering.
Does PhantomJS support MFA?
Do not count on it. Modern identity-provider and MFA flows may not work reliably in its legacy browser engine.
Can I use the same script for every website?
No. Form selectors, redirects, cookies, and authenticated markers are specific to each site.
What does the page.open status tell me?
It reports whether the page load succeeded or failed. It does not certify that login succeeded.
Primary references
- PhantomJS
page.opendocuments the load callback status. - WebPage settings documents HTTP authentication, resource timeout, JavaScript, image loading, and when settings apply.
page.addCookiedocuments cookie insertion and domain matching.page.renderdocuments output formats, quality, and rendering.- Page Automation with PhantomJS covers page scripting and automation.


