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.

Inject CSS after page.open succeeds, inside page.evaluate, by creating a <style> element and appending it to the document head. Pass the stylesheet as a plain string, then render the page. This works because page.evaluate executes DOM code in PhantomJS’s page context while keeping arguments and return values on a simple JSON-serializable boundary.

Inject CSS after page.open succeeds

The reliable sequence is: open the URL, check the status, inject a style element in the page context, render, and exit. The callback below is a complete PhantomJS script.

var page = require('webpage').create();
var css = 'body { background: #f5f5f5; } .notice { color: #b00; }';

page.open('https://example.test/', function (status) {
  if (status !== 'success') {
    console.log('Unable to access network');
    phantom.exit();
    return;
  }

  page.evaluate(function (cssText) {
    var style = document.createElement('style');
    style.setAttribute('type', 'text/css');
    style.appendChild(document.createTextNode(cssText));
    (document.head || document.documentElement).appendChild(style);
  }, css);

  page.render('styled.png');
  phantom.exit();
});

Save it as capture.js and run it with the PhantomJS executable. A successful run writes styled.png. If the network request fails, the script logs the failure and exits without rendering a misleading image.

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

Why the injection belongs in evaluate

page.evaluate is sandboxed in the web-page context. The callback can use ordinary browser APIs such as document.createElement, document.createTextNode, document.head, and CSS selectors. PhantomJS passes the value of css into that callback as a string. Keep it that way: PhantomJS documents that evaluate arguments and return values must be simple JSON-serializable values. Functions, DOM elements, and other page objects cannot cross the boundary.

#1 Best Overall
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Do not try to create the style element in the PhantomJS script and pass that node to evaluate. Create the node inside the callback instead. The same rule applies when reading data back: return strings, numbers, booleans, arrays, or plain objects rather than DOM nodes.

Use an external injector when the CSS is reusable

For a stylesheet used by several capture scripts, keep the DOM operation in a separate file. The file below runs in the page context when PhantomJS loads it.

(function () {
  var cssText = 'body { font-family: sans-serif; }';
  var style = document.createElement('style');
  style.type = 'text/css';
  style.appendChild(document.createTextNode(cssText));
  (document.head || document.documentElement).appendChild(style);
}());

Save it as inject-css.js. Load it only after the page has opened:

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 page = require('webpage').create();

page.open('https://example.test/', function (status) {
  if (status !== 'success') {
    console.log('Unable to access network');
    phantom.exit();
    return;
  }

  if (!page.injectJs('inject-css.js')) {
    console.log('CSS injector could not be loaded');
    phantom.exit();
    return;
  }

  page.render('styled.png');
  phantom.exit();
});

The official PhantomJS API defines injectJs(filename) as returning true when the file is loaded and false when it cannot be loaded. Check that boolean before rendering so a missing file does not look like a CSS failure.

When you control the complete HTML, use setContent

If the page is generated locally rather than downloaded, put the CSS directly in the markup and call setContent:

var page = require('webpage').create();
var html = '<!doctype html><html><head><style>body{margin:0}</style></head>' +
           '<body><h1>Report</h1></body></html>';

page.setContent(html, 'https://example.test/report/');
page.render('report.png');
phantom.exit();

setContent reloads the supplied markup, sets the current location to the URL you provide, and does not make an HTTP request. The base URL is useful when the generated document refers to relative assets. Setting page.content also replaces and reloads the main-frame content, but setContent makes the intended base URL explicit.

Choose this method only when you own the document. For an existing remote page, setContent would replace the page rather than modify it; use post-load evaluate or injectJs instead.

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

Make the injected rules affect the screenshot

Inject at the right time

The document must exist before the style element can be appended, so inject from the successful page.open callback. Render, or read computed layout, only after injection. Injecting before navigation is ineffective because navigation creates a new document.

Rank #3
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

Support documents without a normal head

Appending to (document.head || document.documentElement) handles both conventional pages and markup that has no <head> element. It also keeps the injector as a single reusable snippet.

Check specificity and ordering

An injected rule can still lose to a more specific selector or to a later rule with equal specificity. If a change appears to do nothing, inspect the target element and compare the selectors, source order, and media conditions. Keep the selector as narrow as necessary, but make it at least as specific as the rule you intend to override.

Account for scripts that replace the DOM

Some pages rebuild their body after the initial load. If a framework replaces the nodes you styled, the style element may remain while the new markup no longer matches your selectors. Inject after the page’s final mutation when possible, or write selectors that also match the replacement structure. If the page continues changing, capture only after the desired state is present.

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

Prefer inline CSS when network determinism matters

You can add a <link rel="stylesheet" href="..."> element, but that introduces another network request and another completion point. Inline CSS in a style element avoids waiting for a remote stylesheet. Use a link only when you specifically need a separately hosted stylesheet and can ensure it has finished loading before the render.

Remember media rules and the PhantomJS viewport

Media queries evaluate against PhantomJS’s current viewport. Set the viewport before opening the page when your rules depend on width, and make sure the capture dimensions represent the layout you are trying to test. A rule intended for a narrow screen will not apply simply because it exists in the injected text.

Which PhantomJS technique should you choose?

Situation Best method Reason
One page and one stylesheet page.evaluate Pass a CSS string and append a style element immediately after page.open.
The same injector is used by several scripts page.injectJs Centralizes the DOM operation and reports success with a boolean.
You own all of the HTML page.setContent Embeds CSS in generated markup, sets a base URL, and avoids an HTTP request.
You need a separately hosted stylesheet Append a link element Preserves an external CSS file, but requires reliable network completion before rendering.

Troubleshoot a stylesheet that does not appear

  • The page is blank or unchanged: log the status passed to page.open. Continue only when it is success; otherwise the page was not retrieved successfully.
  • The injector never runs: verify the path passed to injectJs and test its return value. A false result means PhantomJS could not load the file.
  • The script throws an error in evaluate: pass only JSON-safe values such as the CSS string. Move all DOM creation and DOM inspection into the callback.
  • The selector matches nothing: inspect the page’s actual markup. A class may be added later, renamed by a script, or replaced when the application rerenders.
  • The rule is overridden: compare specificity, source order, and media conditions. Test the selector against the element you expect to change.
  • The page has no head: append to document.documentElement through the head-or-root fallback shown in the examples.
  • The external stylesheet is missing at capture time: replace the link with inline CSS or delay rendering until the stylesheet has loaded. Inline rules remove that extra network dependency.
  • The layout changes after injection: wait for the page’s final DOM state before calling page.render. A style added to an earlier tree cannot style nodes that a later script replaces unless the selectors still match them.

PhantomJS is legacy software

PhantomJS’s project README states: “Important: PhantomJS development is suspended until further notice.” That status matters when you plan a new screenshot pipeline: browser behavior, JavaScript support, and security expectations will not receive ongoing PhantomJS development. The injection patterns above remain useful for maintaining an existing PhantomJS job, reproducing a historical render, or processing a controlled legacy page. For a new service, evaluate a maintained browser or a screenshot API against your compatibility requirements.

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 is a website screenshot API and MCP server. It accepts a URL in one request and returns PNG, JPEG, WebP, or PDF. Its custom CSS and JavaScript options let you alter a page before capture without maintaining a PhantomJS process. The API also handles cookie and consent banners, newsletter popups, and chat widgets before the shot.

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

Here is the smallest request using the documented endpoint (see the ScreenshotNeo API documentation for the capture options, including custom CSS):

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

The same call in 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)

And in 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 reports whether a response was a clean page, a bot check, a blank page, a timeout, a failed load, or a cache hit through response headers. Only clean shots are billed; bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing. An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients, so an AI agent 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, and every feature is available on every plan. If you want to replace the browser setup, create a free ScreenshotNeo account.

FAQ

How can I verify that the injected rule won before rendering?

Run a second page.evaluate that returns a simple value such as getComputedStyle(document.querySelector('.notice')).color. Returning the computed color as a string respects the evaluate boundary and confirms what the page will render.

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.

Can one external injector serve multiple pages?

Yes. Keep the CSS text and style-element code in inject-css.js, then call page.injectJs('inject-css.js') for each page after its own successful page.open. Each page has a separate document, so the injector must run in each page context.

Frequently Asked Questions

How can I verify that the injected rule won before rendering?

Run a second page.evaluate that returns a simple value such as getComputedStyle(document.querySelector('.notice')).color. Returning the computed color as a string respects the evaluate boundary and confirms what the page will render.

Can one external injector serve multiple pages?

Yes. Keep the CSS text and style-element code in inject-css.js, then call page.injectJs('inject-css.js') for each page after its own successful page.open. Each page has a separate document, so the injector must run in each page context.

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.