Skip to main content
Version: 9.x

Screenshot Markdown

Captures Markdown files, laid out by your own HTML page, as a PNG, JPEG or WebP image.

POST/forms/chromium/screenshot/markdown
filesfile[]required

index.html, the Markdown files it places with {{ toHTML "notes.md" }}, and the files the page loads.

archivesfile[]

ZIP files Gotenberg unpacks: each entry counts as a file uploaded under files.

URLs Gotenberg downloads first, as if you had uploaded the files. Each entry takes a url, and optionally extraHttpHeaders.

waitDelayduration

Waits this long once the page loads, before printing: 2s, 500ms. It must be shorter than --api-timeout, or the request answers 400.

Waits until an element matching this CSS selector is visible, such as #chart.ready.

Waits until this JavaScript expression returns true, such as window.ready === true.

Waits until window.status equals this value.

Prints without waiting for the network to go idle. false waits, for pages that fetch content after they load.

Default: true

Prints without waiting for the network to go almost idle. false waits.

Default: true

Answers 409 when the page itself answers one of these statuses. 499 stands for 400 to 499, 599 for 500 to 599, and so on for each hundred.

Default: [499,599]

Answers 409 when a resource the page loads, such as an image or a script, answers one of these statuses. Ranges work as for failOnHttpStatusCodes.

Domains, subdomains included, that failOnResourceHttpStatusCodes leaves out: ["sentry-cdn.com"].

Answers 409 when a resource's load fails on the network: a refused or dropped connection, a host that doesn't resolve, a blocked request. A timeout or a certificate error doesn't count.

Default: false

Answers 409 when the page throws a JavaScript exception.

Default: false

Answers 409 when the page writes one of these console types: log, debug, info, warning, error or assert.

Cookies to set before loading: items with a name, a value and a domain, and optionally a path, secure, httpOnly and sameSite (Strict, Lax or None).

Headers Chromium sends with the requests the page makes: {"X-Token": "abc"}. End a value with ;scope=<regex> to send it only to the URLs the regex matches. At most 64 headers.

userAgentstring

Replaces Chromium's User-Agent header.

The CSS media type the page sees: screen or print.

CSS media features the page sees: {"prefers-color-scheme": "dark"}.

Drops the default white background, for a transparent PDF or image. A PDF needs printBackground too.

Default: false

formatstring

png, jpeg or webp.

Default: png

qualityinteger

The JPEG quality, from 1 to 100. Other formats ignore the value, which must still be in range.

Default: 100

widthinteger

The viewport width, in CSS pixels.

Default: 800

heightinteger

The viewport height, in CSS pixels.

Default: 600

clipboolean

Captures the viewport only. Without it, the image is as tall as the page.

Default: false

selectorstring

Captures the first element matching this CSS selector, over clip. No visible match answers 400.

Image pixels per CSS pixel: 2 doubles the resolution.

Default: 1

Encodes faster, for a larger file.

Default: false

The image, as PNG unless format picks another.

The route also takes the headers. An error answers a status and a message: see Errors.

Capture the page​

Upload an index.html that places each Markdown file with {{ toHTML "<name>" }}, the Markdown files and the page's assets, as on the Markdown conversion:

curl \
--request POST http://localhost:3000/forms/chromium/screenshot/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
-o notes.png

Pick the format​

curl \
--request POST http://localhost:3000/forms/chromium/screenshot/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
--form format=jpeg \
--form quality=80 \
-o notes.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/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
--form width=1280 \
--form height=720 \
--form clip=true \
-o notes.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/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
--form selector=h1 \
-o notes.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/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
--form deviceScaleFactor=2 \
--form clip=true \
-o notes.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/markdown \
--form files=@index.html \
--form files=@notes.md \
--form files=@style.css \
--form omitBackground=true \
-o notes.png

Screenshots take no stages: a request with it answers 400. To render a PDF's pages as images, see the render stage.

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:

index.html
<!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)