ScreenshotNeo

BlogHow-to

How to Add Custom CSS to an HTMLCSStoImage Screenshot

Add CSS to an HTMLCSStoImage URL or HTML capture, with runnable cURL, Python, and Node.js examples, render options, and troubleshooting.

By the ScreenshotNeo team4 October 20268 min read

To add custom CSS to an HTMLCSStoImage screenshot, send a POST request to https://hcti.io/v1/image with either a public-page url or your own html, plus a css string. Authenticate with HTTP Basic Auth: your API ID is the username and your API Key is the password. The css field is optional. See the official API instructions.

Choose URL capture or HTML rendering

Use url when HTML/CSS to Image should load a public web page and inject your CSS into it. Use html when you are providing the markup to render. The request needs one of these inputs; include css when you want to apply style overrides.

  • URL capture: best for changing the look of an existing public page without editing its source.
  • HTML rendering: best for rendering a component, card, report, or other markup you construct yourself.

Keep the API ID and key on a server or in a local environment, not in frontend JavaScript, a public repository, or a page delivered to users. The API key is a credential that can be used for operations allowed by the account.

Send CSS with cURL

Set HCTI_USER_ID and HCTI_API_KEY in your shell environment, then run:

curl -X POST https://hcti.io/v1/image \
  -u "$HCTI_USER_ID:$HCTI_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://example.com","css":"body { background: #f4f4f4; } h1 { color: #174ea6; }"}'

This sends JSON and uses Basic Auth. The response is the rendered image response; save it to a file if you want a local artifact. The request shape and authentication method follow the API documentation.

Send CSS with Python

Install the requests package, export the same credential variables, and run this script:

import os
import requests

user_id = os.environ["HCTI_USER_ID"]
api_key = os.environ["HCTI_API_KEY"]

response = requests.post(
    "https://hcti.io/v1/image",
    auth=(user_id, api_key),
    json={
        "url": "https://example.com",
        "css": "body { background: #f4f4f4; } h1 { color: #174ea6; }",
    },
    timeout=90,
)
response.raise_for_status()

content_type = response.headers.get("Content-Type", "")
extension = "png" if "png" in content_type else "jpg"
with open(f"screenshot.{extension}", "wb") as image_file:
    image_file.write(response.content)

For an HTML render, replace the url entry with an html string. Check the response content type and any error response before treating the bytes as a valid image.

Send CSS with Node.js

This example uses Node’s built-in fetch. Set HCTI_USER_ID and HCTI_API_KEY in the process environment:

const userId = process.env.HCTI_USER_ID;
const apiKey = process.env.HCTI_API_KEY;
if (!userId || !apiKey) throw new Error("Set HCTI_USER_ID and HCTI_API_KEY");

const auth = Buffer.from(`${userId}:${apiKey}`).toString("base64");
const response = await fetch("https://hcti.io/v1/image", {
  method: "POST",
  headers: {
    Authorization: `Basic ${auth}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    url: "https://example.com",
    css: "body { background: #f4f4f4; } h1 { color: #174ea6; }",
  }),
});

if (!response.ok) {
  throw new Error(`HTML/CSS to Image returned HTTP ${response.status}: ${await response.text()}`);
}

const image = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) => writeFile("screenshot.png", image));

As with Python, use html instead of url when supplying your own markup. Confirm the actual returned format before assigning a filename extension in production.

Write CSS overrides that survive the page

When capturing a URL, the site already has its own stylesheets. Use selectors specific enough to override the target rules, and scope changes to the region you intend to alter. For example:

body {
  background: #f4f4f4;
}
main article h1 {
  color: #174ea6;
}
.cookie-banner,
.newsletter-modal {
  display: none !important;
}

Do not assume every site uses those class names; inspect the page markup and replace them with selectors that exist on the target. A broad rule such as * { display: none } can hide the content you need. Avoid relying on selectors for dynamically generated class names that may change between page loads.

When you supply html, include the markup required for the image and use the css field for its styling. The CSS field also matters for transparent backgrounds: the FAQ recommends passing the background CSS through css, and identifies transparent_background: true as the simplest PNG option. See the official FAQ.

Configure the capture

These documented options shape what gets rendered. Refer to the API guide and URL-to-image guide for current limits and plan requirements.

Option Use Important distinction
url Load a public page and inject the CSS you send. Use this for an existing web page.
html Render markup supplied in the request. Use this instead of url for your own HTML.
css Apply CSS to the submitted markup or inject overrides into the URL page. This is the style text, not an element crop selector.
selector Capture or crop a matching element. It identifies what to capture; it does not add styling.
full_screen Request a full-page URL screenshot. Use for content extending beyond the initial viewport.
width, height Set capture viewport dimensions. Query parameters are documented for URL screenshots; check current limits.
device_scale Control device pixel scale. Higher scale can produce more pixels and larger output.
media_type Select screen or print media CSS. The page may have different rules under print media.
ms_delay Wait a fixed number of milliseconds before capture. Use when page content appears after load; longer waits add latency.
render_when_ready Wait for the page’s readiness signal. The page can call ScreenshotReady() when its render is complete.
transparent_background Request a transparent PNG background. For transparency, the FAQ recommends the option or CSS through css.

Check the current API reference for exact parameter placement and accepted values before building a production integration around a limit or format detail.

Wait for JavaScript-driven content

A page can return an initial document before its client-side application, charts, or images are ready. If the capture is incomplete, use ms_delay to add a wait. When you control the page, render_when_ready and the ScreenshotReady() callback let the page signal readiness instead of relying only on a fixed delay. The service describes both approaches in its FAQ and API guide.

For repeated captures, prefer a readiness signal when you can add one to the page: a fixed delay that is long enough for the slowest run wastes time on fast runs, while a short delay can still capture too early. If the target is outside your control, increase the delay gradually and verify that the needed content has appeared.

Common problems and fixes

Symptom Likely cause What to check
Authentication fails Wrong API ID/key, malformed Basic Auth, or credential variables are empty. Confirm the dashboard values and that the ID is the username and key is the password. Keep credentials server-side.
CSS has no visible effect Selector does not match, existing rules are more specific, or the target DOM differs from expectation. Inspect the live page’s selectors, scope the override accurately, and use !important only when needed.
Screenshot shows old or incomplete content JavaScript or lazy content was not ready at capture time. Increase ms_delay or configure render_when_ready and call ScreenshotReady() after rendering.
Only one element is needed, but the result includes the page The style field was confused with the capture selector. Use selector to target the element; use css separately for its styling.
Page content is cut off The capture uses viewport dimensions rather than full-page mode. For URL capture, try full_screen; set dimensions as needed and review documented limits.
Transparency is missing Background remains opaque or CSS was placed only in the HTML style block. Use transparent_background: true for PNG or pass transparent-background CSS via the css parameter.
Image bytes are saved with the wrong extension The script assumed a format without checking the response. Inspect the response content type and name the file accordingly.
Requests hang or run out of time The target page is slow or the chosen wait is too long. Set a client timeout, reduce unnecessary delay, and use a page-ready signal when you control the page.

Performance, reliability, and cost considerations

  • Keep the rendered surface small. Capture only the required element with selector when a full page is unnecessary. Full-page output and higher device scale increase the amount of rendered content and image data.
  • Wait for the real readiness condition. A delay improves completeness only if it is long enough, and directly adds waiting time. A readiness callback can avoid arbitrary long waits when you control the page.
  • Treat remote pages as variable input. Site redesigns, changing content, and late-loading resources can alter output. Use stable URLs and selectors, and handle request failures in the caller.
  • Protect credentials and image responses. Keep API credentials out of browser code, check HTTP status before saving, and avoid logging secrets.
  • Check current plan limits before scaling. The reviewed documentation describes available options but this guide does not assert quotas, prices, latency, or service-level guarantees.

Or skip the browser setup

If your job is to get a clean screenshot rather than manage rendering code, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a screenshot or PDF, and its capture options include custom CSS and JavaScript. The ScreenshotNeo API docs describe the request options.

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; each cleanup step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server gives AI agents screenshot, page-info, and PDF capture tools. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, no card required.

FAQ

Can I add CSS when rendering my own HTML?

Yes. Send your markup in html and the styles in css.

Does selector apply CSS?

No. It selects the part of a page to capture. Put style overrides in css.

Can I use print styles?

The API documents media_type choices for screen and print CSS. Consult the current reference for accepted values and constraints.

How do I capture a page with lazy-loaded content?

Wait for the content using a suitable delay or a readiness callback if you control the page. For full-page URL shots, the documented full_screen option requests the whole page; confirm its behavior for your page and current plan.

References