How to Use URL2PNG in a Node.js Website Screenshot Workflow
Build URL2PNG requests in Node.js, save or stream screenshots, choose capture and cache settings, and troubleshoot common integration issues.
Use URL2PNG’s Node.js package to build a screenshot URL, save the returned image stream, or pipe it into an HTTP response. The vendor’s v6 API signs the complete encoded query string with your account secret, so the exact query representation used to make the token must also be sent in the request. Keep the secret on your server. The examples below follow URL2PNG’s documented pattern; they have not been independently run.
URL2PNG’s Quickstart Guide documents the API request format and capture options. Its linked npm package is old: the registry page currently lists version 6.0.2 as published 11 years ago. Check package compatibility and dependency health before adopting it in a new application. Review the url2png npm listing.
1. Install the package and keep credentials server-side
Install the package in the Node.js application that will make the screenshot request:
npm install url2png
Set URL2PNG_API_KEY and URL2PNG_SECRET_KEY in the server’s environment or secret manager. Do not put the secret in browser JavaScript, a public repository, or a URL that users can inspect. A caller who can invoke your screenshot endpoint should not be able to choose arbitrary targets without validation; otherwise, your server could be used to request internal or unintended URLs.
2. Save a screenshot to a file
This CommonJS example uses the documented readURL() stream and waits for the file stream to finish before reporting success. It sets a viewport and a maximum output width:
const url2png = require('url2png')(
process.env.URL2PNG_API_KEY,
process.env.URL2PNG_SECRET_KEY
);
const fs = require('node:fs');
const { pipeline } = require('node:stream/promises');
async function saveScreenshot() {
const target = 'https://example.com/';
const options = {
viewport: '1280x900',
thumbnail_max_width: 800
};
const screenshot = url2png.readURL(target, options);
await pipeline(screenshot, fs.createWriteStream('example.png'));
console.log('Saved example.png');
}
saveScreenshot().catch((error) => {
console.error('Screenshot download failed:', error);
process.exitCode = 1;
});
The package’s documented pattern is CommonJS. If your project uses ES modules, import the package using the interop pattern supported by your Node.js version and the package; verify that pattern against the installed release. The registry’s age makes it especially important to check compatibility before deployment.
3. Stream a screenshot from an HTTP endpoint
You can pipe the screenshot stream directly to an HTTP response instead of writing a local file. Validate the target and handle errors before headers have been sent. This example uses Express and restricts targets to HTTPS URLs on a small allowlist; adjust the policy to your application.
const express = require('express');
const { pipeline } = require('node:stream/promises');
const url2png = require('url2png')(
process.env.URL2PNG_API_KEY,
process.env.URL2PNG_SECRET_KEY
);
const app = express();
const allowedHosts = new Set(['example.com', 'www.example.com']);
app.get('/screenshot', async (req, res) => {
let target;
try {
target = new URL(String(req.query.url || ''));
} catch {
return res.status(400).json({ error: 'Provide a valid URL.' });
}
if (target.protocol !== 'https:' || !allowedHosts.has(target.hostname)) {
return res.status(400).json({ error: 'Target host is not allowed.' });
}
res.setHeader('Content-Type', 'image/png');
res.setHeader('Cache-Control', 'private, max-age=300');
try {
await pipeline(
url2png.readURL(target.toString(), {
viewport: '1280x900',
thumbnail_max_width: 800
}),
res
);
} catch (error) {
console.error('URL2PNG stream failed:', error);
if (!res.headersSent) {
res.status(502).json({ error: 'Screenshot service request failed.' });
} else {
res.destroy(error);
}
}
});
app.listen(3000, () => console.log('Listening on port 3000'));
Set the response content type to match the returned format. The package examples save a PNG; if your application requests another format through its API integration, update the filename and response header accordingly. Avoid returning provider credentials or signed request URLs in error messages.
4. Understand URL2PNG v6 request signing
A v6 URL contains the API key, a token, the image path, and a query string. URL2PNG documents the token as the MD5 hash of the entire query string followed by the secret key. Build the query string once, sign that exact string, and append the same string to the request. Reordering options or changing percent encoding after signing can produce an invalid token.
The package’s buildURL() method is convenient when you need the URL, for example to put it in an image source or pass it to a downloader:
const url2png = require('url2png')(
process.env.URL2PNG_API_KEY,
process.env.URL2PNG_SECRET_KEY
);
const screenshotUrl = url2png.buildURL('https://example.com/', {
viewport: '1280x900',
fullpage: true,
thumbnail_max_width: 1000
});
console.log(screenshotUrl);
Do not log signed URLs indiscriminately: they contain a request token and target details. If you build requests yourself instead of using the package, follow the v6 documentation’s precise encoding and signing format, and add a test that compares the signed query with the transmitted query. The vendor’s Python sample sorts and URL-encodes options before calculating the token.
Python example for a signed v6 URL
This example prints a URL using the same documented pattern: URL-encode the complete query, hash that query plus the secret, then place the token and query in the request URL.
import hashlib
import os
from urllib.parse import urlencode
api_key = os.environ['URL2PNG_API_KEY']
secret = os.environ['URL2PNG_SECRET_KEY']
options = {
'url': 'https://example.com/',
'fullpage': 'true',
'thumbnail_max_width': '1000',
'viewport': '1280x900',
}
query_string = urlencode(sorted(options.items()))
token = hashlib.md5((query_string + secret).encode('utf-8')).hexdigest()
screenshot_url = (
f'https://api.url2png.com/v6/{api_key}/{token}/png/?{query_string}'
)
print(screenshot_url)
Use this to understand or generate a signed URL; do not expose the secret to an untrusted client. URL2PNG’s official quickstart includes other language samples and the v6 request anatomy.
cURL example with a precomputed token
cURL can download the image once you have calculated the token for the exact query string. This example assumes URL2PNG_TOKEN was generated server-side using the account secret and the exact query below:
curl --fail --show-error --location \
"https://api.url2png.com/v6/${URL2PNG_API_KEY}/${URL2PNG_TOKEN}/png/?url=https%3A%2F%2Fexample.com%2F&fullpage=true&viewport=1280x900" \
--output screenshot.png
Do not substitute or reorder query values after generating the token. For production scripts, check the exit status and verify that the saved response is an image rather than an API error body.
5. Choose capture and freshness options
| Option | What it controls | When to use it |
|---|---|---|
viewport |
Browser viewport as a width-by-height string. The quickstart documents a default of 1480x1037. |
Set a consistent desktop or mobile-sized layout for thumbnails or visual comparisons. |
fullpage=true |
Asks URL2PNG to attempt capturing the entire document canvas. The documented default is viewport-only. | Use for long pages when the output should include content below the fold. |
thumbnail_max_width |
Constrains the screenshot width. Without scaling, the image is returned 1:1. | Reduce image dimensions for cards or previews. Check whether downscaling makes text too small. |
unique |
Varies the request to force a fresh screenshot; the docs suggest a timestamp. | Change it when content freshness matters and a cached result is not acceptable. A fixed value can intentionally reuse a capture. |
ttl |
Sets cache time to live in seconds. The documented default is 2592000 seconds (30 days). |
Choose a cache lifetime that matches how often the target page changes. |
say_cheese=true |
Waits until the page contains <div id='url2png-cheese'></div>. |
Use when you control the target page and can add a clear readiness marker after its content is ready. |
delay |
Adds a fixed delay in seconds after document readiness and asset loading. | Use for known late-running animations or scripts. A long delay increases response time and does not guarantee a particular application state. |
custom_css_url |
Injects a stylesheet from a URL. | Apply target-specific visual adjustments when the target and stylesheet are accessible to the renderer. |
user_agent |
Overrides the user-agent header. | Use only when the target must render for a specific user-agent; the default is not specified as a custom value. |
accept_languages |
Overrides the Accept-Language header. The documented default is en-US,en;q=0.8. |
Request localized page content when the site selects language from this header. |
protocol |
The package example accepts https, http, or blank for protocol-relative URLs. |
Set it to match the intended target scheme. Prefer HTTPS when the site supports it. |
There is a package-doc discrepancy to account for: the current quickstart documents a default viewport of 1480x1037, while the npm README lists 1280x1024 and a maximum of 4000x4000. Set the viewport explicitly and confirm accepted limits with the current vendor documentation if you depend on a specific dimension.
Likewise, the package README describes a force option for cache use, while the current quickstart describes unique and ttl. Treat these as distinct documented interfaces rather than assuming one is an alias for another. Confirm the installed package’s behavior before relying on force.
6. Pick a delivery pattern
- Write locally: Stream to a file when a background job will upload the result to object storage or attach it to another artifact. Wait for the stream to finish before declaring the job complete.
- Stream to a client: Pipe the response when a caller needs the image immediately. Handle a failure that occurs after response headers or bytes have already been sent.
- Build a URL: Use
buildURL()when another layer will fetch or embed the screenshot. Signed URLs are credentials for a specific request; avoid exposing them where logs or analytics capture query strings. - Store and reuse: Cache your own result when many users request the same target and options. Make the cache key include the target and all capture settings that affect output.
7. Troubleshoot common failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Authentication or invalid-token response | The token was calculated from a differently encoded, ordered, or incomplete query than the one sent. | Construct the complete query once, sign that exact string plus the secret, and send it unchanged. Keep parameter ordering deterministic. |
| Image file contains text or is corrupt | The response may be an error document or an interrupted stream saved with an image extension. | Check the HTTP response and content type in your downloader, use pipeline error handling, and only mark the file successful after completion. |
| Screenshot shows the wrong viewport | The viewport was omitted, malformed, or interpreted according to a different package/API default. | Set an explicit WIDTHxHEIGHT value and verify the output dimensions. |
| Below-the-fold content is missing | Viewport-only capture is the documented default. | Set fullpage: true; the service says it will attempt to capture the whole document canvas. |
| Page is captured before client-rendered content appears | The page’s asynchronous state was not ready when capture occurred. | If you control the page, add the documented readiness element and enable say_cheese. Otherwise test a modest fixed delay; neither control guarantees every third-party or asynchronous state. |
| New content does not appear | A cached screenshot is being reused under the current cache settings. | Adjust TTL or vary unique when a fresh render is required. Remember each fresh render may count toward the plan allowance. |
| HTTP endpoint returns an error after starting the image response | The upstream stream failed after headers were sent. | Log the failure and close/destroy the response; a JSON error cannot replace bytes already sent. Consider buffering to a temporary file if all-or-nothing delivery is required. |
| Module import or runtime compatibility issue | The linked npm package is old and may not match the current Node.js module system or runtime expectations. | Check the installed package metadata and repository, test it against your supported Node.js version, or implement the documented v6 signing request directly. |
8. Performance, reliability, and cost
For a fast path, reuse cached captures when the target and capture settings have not changed. URL2PNG says cached screenshots do not count against the plan’s fresh-render allowance; its plans page also says the default cache period is 30 days, adjustable through TTL. A new unique value forces a fresh screenshot and therefore changes the render/reuse pattern.
Full-page screenshots, extra fixed delay, and large viewports can increase the time and size of a response. Use the smallest viewport and output width that satisfies the consuming interface. Set application-level timeouts, limit concurrent screenshot jobs, and retry only transient failures with a bounded retry policy; retries that force fresh renders can add usage.
The plans page reviewed for this guide lists 5,000 fresh screenshots for $29/month, 20,000 for $99/month, and 50,000 for $199/month, with paid overages shown for those plans. It says cached screenshot loads do not count as fresh renders and says it does not offer free accounts. Pricing and allowances can change, so verify them on the official URL2PNG plans page before budgeting. Estimate from expected fresh captures, not total image views.
9. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a PNG, JPEG, WebP, or PDF. Cookie banners are accepted and removed before capture; 60+ known consent platforms, newsletter popups, and chat widgets can be removed, and each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits cost nothing, with the page verdict and billing status reported in response headers. An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000.
For a full list of parameters and response behavior, see the ScreenshotNeo API documentation.
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Or from a shell:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Or 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)
Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.
10. FAQ
Can I use URL2PNG from browser-side JavaScript?
Keep the private key and request signing on a server. A browser-side integration that exposes the secret also exposes the ability to generate signed requests.
Does full-page mode guarantee every page element is included?
No. The documentation says it attempts to capture the entire document canvas. Extremely long pages and dynamic content should be checked against your actual target pages.
Should I use a timestamp for every request’s unique value?
Only when each request needs a fresh render. A changing value prevents reuse for that request pattern and can increase fresh-render usage.
Can I serve the screenshot directly from URL2PNG?
The documented Node.js example pipes the response to an HTTP response. For public, high-traffic assets, consider storing the image and serving it through your application’s normal caching layer.
Sources
- URL2PNG Quickstart Guide: v6 request signing, Node.js example, options, and documented defaults.
- URL2PNG Plans: listed allowances, prices, fresh-render counting, and cache behavior.
- url2png on npm: package README, version, and publication metadata.


