ScreenshotNeo

BlogHow-to

Generate Images from HTML and CSS with n8n

Build an n8n workflow that turns HTML and CSS into PNG, JPEG, or WebP images with a screenshot API, then stores or sends the result.

By the ScreenshotNeo team1 October 202610 min read

Direct answer: n8n orchestrates the workflow, while a browser-based screenshot API renders the HTML and CSS. In n8n, trigger a workflow, build an HTTP Request containing either a URL or inline HTML, send it to a screenshot endpoint, receive binary image data, and pass that binary to storage, email, a CMS, or another service.

For a hosted renderer, Browserless documents a POST /screenshot endpoint that accepts a URL or inline HTML and returns PNG, JPEG, or WebP data. Its screenshot options include controls such as full-page capture and image type. See the Browserless screenshot API documentation.

What the n8n workflow does

n8n connects the trigger, data preparation, rendering request, and downstream file handling. It is an automation tool that connects applications and APIs; it is not itself the browser renderer. The renderer must load the page, apply CSS, run browser-side JavaScript, and encode the resulting pixels.

  1. Start with a Manual Trigger, Webhook, Schedule Trigger, or another n8n trigger.
  2. Prepare a URL or complete HTML document in a Set, Code, or previous application node.
  3. Use an HTTP Request node to call the screenshot service.
  4. Configure the response as a file or binary result.
  5. Send the binary output to storage, an email node, a CMS, an upload API, or a webhook response.

Prerequisites

  • An n8n Cloud or self-hosted instance. The n8n documentation and product site describe both deployment models.
  • An account and API token for the screenshot service you choose.
  • HTML that includes the styles required for the final image.
  • A destination for the resulting binary data, such as object storage or an HTTP upload endpoint.

Build a URL screenshot workflow in n8n

1. Add a trigger

Create a new workflow and add Manual Trigger while developing. Replace it later with a Webhook or Schedule Trigger when the input should arrive automatically.

2. Add the HTTP Request node

Use an HTTP Request node after the trigger. Configure:

Setting Value
Method POST
URL Your provider’s screenshot endpoint
Authentication Provider token stored in n8n Credentials
Request body JSON containing the URL and screenshot options
Response format File/binary
Binary property A predictable name such as data or screenshot

Browserless’s n8n guide uses an HTTP Request node, stores the token in n8n Credentials, and sends it as a query parameter. Keep credentials out of Set nodes, Code nodes, exported workflow JSON, and log messages.

3. Send a URL and capture options

A typical JSON body for a URL capture looks like this:

{
  "url": "https://example.com",
  "fullPage": true,
  "type": "png"
}

Use the exact field names documented by your provider. Browserless documents URL screenshots, inline HTML, full-page capture, and PNG, JPEG, or WebP output.

4. Handle the binary result

When the HTTP Request node returns a file, connect it to the next node without converting it to JSON. Common downstream choices include:

  • Object storage upload nodes.
  • Email nodes with the binary property attached.
  • CMS or media upload APIs.
  • A Respond to Webhook node for an image-producing endpoint.

Render inline HTML and CSS

Inline HTML is useful when the graphic is generated from workflow data: invoices, social cards, certificates, product labels, or reports. Send the complete document in the request body using the renderer’s html field. Do not send both html and url in the same Browserless request.

{
  "html": "<!doctype html><html><head><style>body{margin:0;background:#101828;color:white;font-family:Arial,sans-serif}.card{width:1200px;padding:80px;box-sizing:border-box}h1{font-size:72px;margin:0 0 24px}</style></head><body><main class='card'><h1>{{ $json.title }}</h1><p>{{ $json.subtitle }}</p></main></body></html>",
  "type": "png",
  "fullPage": true
}

In n8n, construct this value with an expression or Code node. Escape quotes and line breaks correctly, and treat data inserted into the HTML as untrusted input. If values can contain user input, HTML-escape them before interpolation.

Example Code node for safe basic escaping

const escapeHtml = (value) => String(value ?? '')
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;')
  .replace(/"/g, '&quot;')
  .replace(/'/g, '&#39;');

const title = escapeHtml($json.title);
const subtitle = escapeHtml($json.subtitle);

return [{
  json: {
    html: `<!doctype html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    * { box-sizing: border-box; }
    body { margin: 0; background: #101828; color: #fff; font-family: Arial, sans-serif; }
    .card { width: 1200px; min-height: 630px; padding: 80px; display: flex; flex-direction: column; justify-content: center; }
    h1 { margin: 0 0 24px; font-size: 72px; line-height: 1.05; }
    p { margin: 0; font-size: 30px; color: #cbd5e1; }
  </style>
</head>
<body>
  <main class="card"><h1>${title}</h1><p>${subtitle}</p></main>
</body>
</html>`
  }
}];

Set the image dimensions deliberately

CSS dimensions and browser viewport dimensions are separate concerns. Set the viewport in the screenshot request when the provider supports it, and make the root element’s dimensions explicit. For a 1200×630 social image, use a 1200px-wide root container and a matching viewport. For responsive pages, test at every viewport your workflow will produce.

Goal Recommended approach
Fixed social card Fixed root width and height; disable unwanted overflow
Whole web page Use full-page capture and verify lazy-loaded content
One component Wrap the component in a fixed-size root element or use an element capture option
High-density output Use a device scale option if the provider supports it, then check file size

Assets, fonts, and external resources

A renderer must be able to reach every external stylesheet, font, image, and script. Inline critical CSS and small assets when deterministic output matters. For remote resources:

  • Use absolute HTTPS URLs.
  • Confirm that the rendering service can access the host.
  • Allow enough time for fonts and images to load.
  • Use a wait condition or delay when the page is populated by JavaScript.
  • Inspect the output in the same deployment environment used by the workflow.

The documented API behavior establishes inline HTML support, but it does not guarantee that every external dependency will load. Validate your own fonts, assets, viewport, and full-page behavior.

Useful screenshot options

Option names vary by provider. Browserless documents screenshot options such as image type and full-page capture. Common controls to look for are:

  • Image type: PNG for lossless graphics, JPEG for smaller photographic files, or WebP where consumers support it.
  • Full page: Captures the document’s full scrollable area instead of only the viewport.
  • Viewport: Sets width and height for responsive layouts.
  • Device scale: Produces higher-density pixels.
  • Delay or wait condition: Gives client-side rendering time to finish.
  • Element selector: Captures one component when supported.
  • Background: Controls transparency or page background when supported.

cURL example

curl -X POST "https://your-renderer.example/screenshot" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_TOKEN" \
  --data '{
    "html": "<html><body><h1>Hello from n8n</h1></body></html>",
    "type": "png",
    "fullPage": true
  }' \
  --output image.png

Replace the placeholder endpoint and authentication scheme with the provider’s documented values.

Python example

import requests

payload = {
    "html": """<!doctype html>
<html><body style='margin:0'>
  <h1>Hello from n8n</h1>
</body></html>""",
    "type": "png",
    "fullPage": True,
}

response = requests.post(
    "https://your-renderer.example/screenshot",
    headers={"Authorization": "Bearer YOUR_TOKEN"},
    json=payload,
    timeout=90,
)
response.raise_for_status()
with open("image.png", "wb") as image_file:
    image_file.write(response.content)

Node.js example

const payload = {
  html: `<!doctype html>
<html><body style="margin:0">
  <h1>Hello from n8n</h1>
</body></html>`,
  type: 'png',
  fullPage: true,
};

const res = await fetch('https://your-renderer.example/screenshot', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN',
  },
  body: JSON.stringify(payload),
});

if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const image = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('image.png', image);

Or skip the browser setup

ScreenshotNeo provides a one-request website screenshot API and supports HTML/CSS-to-image workflows. Its API can return PNG, JPEG, WebP, or PDF, and its options include custom CSS and JavaScript, viewport and device presets, retina scale, waits, hidden selectors, request blocking, cookies, headers, caching, element capture, and full-page capture.

Use the API directly from an n8n HTTP Request node, or call it from your application:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. Its MCP server lets Claude, Cursor, and other MCP clients use take_screenshot, get_page_info, and capture_pdf. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. See the ScreenshotNeo API documentation.

Start with 1,000 free screenshots a month and no card.

Alternative: the HTML/CSS to Image n8n node

The HTML/CSS to Image project README describes an n8n community node for generating images, website screenshots, PDFs, and render-on-demand URLs through its API. It also describes CSS overrides and binary output for downstream file nodes. Treat it as an optional integration: verify the current node package, authentication steps, supported options, and deployment compatibility before using it in production.

Security checklist

  • Store provider tokens in n8n Credentials.
  • Do not place secrets in workflow expressions, HTML, URLs, or sample payloads that reach logs.
  • Escape user-provided text before inserting it into HTML.
  • Decide whether remote images, fonts, and scripts are allowed to load.
  • Review the renderer’s current guidance for sandboxing, network access, and limits before rendering arbitrary untrusted HTML.
  • Restrict who can invoke a public webhook that triggers paid renders.

Performance, reliability, and cost

Performance

  • Reuse a fixed template instead of generating large, duplicated markup.
  • Inline small critical assets and compress large images.
  • Wait only for the selector or network condition that proves the page is ready.
  • Choose JPEG or WebP when lossless PNG is unnecessary.
  • Use caching where identical inputs are rendered repeatedly.

Reliability

  • Set an HTTP timeout longer than the renderer’s normal page-load time.
  • Retry transient 5xx and network failures with bounded exponential backoff.
  • Make downstream writes idempotent by deriving a stable filename or record key from the input.
  • Log the source identifier, render options, status code, and output size, but never log credentials.
  • Keep a fallback response for pages whose external assets fail to load.

Cost

Rendering costs depend on the service, plan, image size, and request volume. Measure your own workflow rather than assuming a latency or quality figure. Cache repeated renders, avoid unnecessary retries, and select the smallest output format that meets the use case. ScreenshotNeo bills only clean shots; bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with the billing result returned in headers.

Troubleshooting

Symptom Likely cause Fix
HTTP 401 or 403 Missing, expired, or incorrectly placed token Move the token to n8n Credentials and follow the provider’s required header or query parameter format.
Image is blank HTML has no visible content, CSS hides it, or rendering finished too early Check the generated HTML, remove accidental display:none, and add a documented wait condition or delay.
Fonts differ Remote font was unreachable or not loaded before capture Use an accessible HTTPS font, inline a critical font where appropriate, and wait for font loading.
Images are missing Relative URLs, blocked requests, or page capture before image load Use absolute URLs, confirm network access, and wait for the image selector.
Only the viewport appears Full-page option is absent or incorrectly named Enable the provider’s full-page option and verify its exact spelling.
n8n shows JSON instead of an image HTTP Request response is configured as JSON Set the response format to File/Binary and choose a binary property.
Workflow times out Heavy scripts, slow assets, or an overly short timeout Reduce page weight, block unnecessary resources where supported, and increase the node timeout within provider limits.
HTML request fails validation Both url and html were supplied or the JSON is malformed Send one input mode and validate the final request body.
Output is unexpectedly expensive Duplicate renders, retries, or missing cache Deduplicate jobs, use a cache, and retry only transient failures.

FAQ

Does n8n render HTML by itself?

No. n8n coordinates the workflow; a browser screenshot service performs the rendering.

Can I generate an image without hosting the HTML on a public URL?

Yes. Use a screenshot endpoint that accepts inline HTML and send the complete document in the request body.

Should I use PNG, JPEG, or WebP?

PNG suits sharp text and transparent graphics. JPEG is useful for photographs. WebP can reduce file size when your destination supports it.

How do I return the image from an n8n webhook?

Configure the screenshot request as binary, then connect it to Respond to Webhook and return the binary property with the correct content type.

Is a community node required?

No. An HTTP Request node is enough for a documented screenshot API. A dedicated community node can be convenient when its current capabilities match your workflow.

Final checklist

  • Choose URL or inline HTML input.
  • Store the API token in n8n Credentials.
  • Set viewport, image type, and full-page behavior explicitly.
  • Wait for fonts, images, and client-side content.
  • Return the response as binary data.
  • Escape user input and review remote-resource access.
  • Cache repeated renders and retry only transient failures.
  • Inspect the final image in the deployment environment.