Screenshot a URL
Captures any page Chromium can reach as a PNG, JPEG or WebP image.
Capture a page
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
-o example.png
Pick the format
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form format=jpeg \
--form quality=80 \
-o example.jpeg
format takes png, the default, jpeg or webp. quality applies to JPEG.
Size the window
width and height set the browser window, in CSS pixels. clip keeps that window only, instead of the whole page:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form width=1280 \
--form height=720 \
--form clip=true \
-o example.png
Without them, the image is 800 pixels wide and as tall as the page.
Capture one element
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form selector=p \
-o example.png
The image takes the bounds of the first element the CSS selector matches. No visible match answers 400.
Sharpen it
deviceScaleFactor sets the image pixels per CSS pixel. At 2, an 800 by 600 window gives a 1600 by 1200 image:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form deviceScaleFactor=2 \
--form clip=true \
-o example.png
Drop the background
omitBackground leaves the white default background out, so a PNG stays transparent where the page paints nothing:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form omitBackground=true \
-o example.png
Screenshots take no stages: a request with it answers 400. To render a PDF's pages as images, see the render stage.
Log in
Send the cookies a session needs. Each takes a name, a value and a domain:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form 'cookies=[
{ "name": "session", "value": "abc123", "domain": "example.com" }
]' \
-o example.png
Or send a header, such as a token. End its value with ;scope= and a regex, and Chromium sends it only to the URLs the regex matches, not to the third-party scripts the page loads:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form-string 'extraHttpHeaders={"Authorization":"Bearer abc123;scope=https://example\\.com/.*"}' \
-o example.png
The value holds a ;, so cURL sends it with --form-string: --form would cut it there. userAgent replaces Chromium's User-Agent header, for sites that answer bots differently.
Fail on errors
A page that answers a 4xx or 5xx status fails the request with 409 Conflict, rather than capturing the error page. After a redirect, the status of the page it lands on counts:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com/missing \
-o example.png
failOnHttpStatusCodes changes the statuses. The resources the page loads, failed loads and the console can fail the request too:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form 'failOnResourceHttpStatusCodes=[
499,
599
]' \
--form failOnConsoleExceptions=true \
-o example.png
499 stands for every status from 400 to 499, and 599 for 500 to 599. failOnResourceLoadingFailed fails the request when a resource's load fails on the network, with a refused connection or a host that doesn't resolve for instance; a timeout or a certificate error doesn't count.
Reach private addresses
Chromium reaches any address Gotenberg's network reaches, private ones included, such as http://intranet.local/report. Operators restrict it with --chromium-deny-private-ips, --chromium-allow-list and --chromium-deny-list: see Restrict outbound requests. A blocked URL answers 403.
Wait for the page
Scripts run, unless the operator turned them off with --chromium-disable-javascript. Chromium captures the page as soon as it loads, though. A page that draws later with JavaScript, such as a chart or a table filled from an API, needs a wait. The examples upload this page, whose chart draws a second after it loads, to the HTML route; the fields work the same on every Chromium route:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Sales report</title>
</head>
<body>
<h1>Sales report</h1>
<div id="chart">Loading…</div>
<script>
// Stands in for a chart that draws once its data arrives.
setTimeout(() => {
const chart = document.getElementById("chart");
chart.textContent = "Revenue: 42,000 EUR";
chart.className = "ready";
window.chartReady = true;
window.status = "ready";
}, 1000);
</script>
</body>
</html>
Without a wait, the result reads "Loading…":
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@index.html \
-o report.png
Each wait below gets "Revenue: 42,000 EUR" instead.
Wait for an element
Chromium waits until an element matching the CSS selector is visible:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@index.html \
--form waitForSelector=#chart.ready \
-o report.png
Mark the page ready from your own code, and wait for the mark. It's the most precise signal.
Wait for an expression
Chromium evaluates the JavaScript expression until it returns true:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@index.html \
--form 'waitForExpression=window.chartReady === true' \
-o report.png
An expression that throws, such as one that reads a property of undefined, answers 400.
Wait for the window status
Chromium waits until window.status equals the value:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@index.html \
--form waitForWindowStatus=ready \
-o report.png
Wait a fixed delay
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@index.html \
--form waitDelay=2s \
-o report.png
A delay is the last resort. Too short, the chart is missing. Too long, every request pays for it.
Wait for the network
skipNetworkIdleEvent=false waits until the page stops making requests, for pages that fetch their content once they load:
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/url \
--form url=https://example.com \
--form skipNetworkIdleEvent=false \
-o example.png
Order and limits
Chromium waits for the page to load, then for waitForWindowStatus, waitForExpression, waitForSelector and waitDelay, in that order. Every wait counts against --api-timeout, 30 seconds by default. A wait that never ends answers 503 Service Unavailable then, and names itself:
The selector '#never' (waitForSelector) did not become visible within the time limit. Increase it with --api-timeout (API_TIMEOUT), or check the selector.
waitDelay must be shorter than --api-timeout. A longer one answers 400 right away, and names the flag:
Invalid form data: form field 'waitDelay' is invalid (got '60s', must be shorter than the API timeout of 30s: raise --api-timeout (API_TIMEOUT) or lower the delay)