Skip to main content

Screenshot HTML

Captures a screenshot of an index.html file (and optional assets) using Headless Chromium. Reference additional CSS, images, and fonts in your HTML using relative paths.

See the Chromium module configuration for startup and behavior flags.

Basics

POST/forms/chromium/screenshot/html
Headers
Gotenberg-Output-Filenamestring
The filename of the resulting file - Gotenberg automatically appends the file extension. Defaults to a random UUID filename.
Gotenberg-Tracestring
A custom request ID to identify the request in the logs; overrides the default UUID.
Form Files
index.htmlfilerequired
An HTML file named 'index.html' to convert into an image.
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
-o my.jpeg
The screenshot has been successfully created.
Content-Disposition: attachment; filename={output-filename.ext}
Content-Type: {content-type}
Content-Length: {content-length}
Gotenberg-Trace: {trace}
Body: {output-file}

Assets

Send images, fonts, and stylesheets alongside your HTML.

cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html
--form files=@/path/to/img.png \
-o my.jpeg

All uploaded files are stored in a single flat directory. Reference assets by filename only: no absolute paths (/img.png) or subdirectories (./assets/img.png).

✅ Correct Reference

Since index.html and logo.png sit side-by-side in the container:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My PDF</title>
</head>
<body>
<h1>Hello world!</h1>
<img src="logo.png" />
</body>
</html>

❌ Incorrect Reference

Gotenberg does not recreate your local folder structure (e.g., images/) inside the container.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>My PDF</title>
</head>
<body>
<h1>Hello world!</h1>
<img src="/images/logo.png" />
</body>
</html>

For remote URLs (CDNs, Google Fonts), make sure the container can resolve the domain. You cannot use localhost from inside the container. Use host.docker.internal or your host's network IP instead.

For small assets, Base64 data URIs eliminate network dependencies entirely. Avoid the HTML <base> element: it breaks the link between your HTML and uploaded assets.

Rendering Behavior

Form Fields
widthnumber
The device screen width in pixels.
Default:800
heightnumber
The device screen height in pixels.
Default:600
clipboolean
Define whether to clip the screenshot according to the device dimensions.
Default:false
formatenum
The image compression format. Options: 'png', 'jpeg', 'webp'.
Default:png
qualitynumber
The compression quality (0-100). Only used if format is 'jpeg'.
Default:100
omitBackgroundboolean
Hides the default white background to allow transparency.
Default:false
optimizeForSpeedboolean
Optimizes image encoding for speed rather than resulting file size.
Default:false
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form width=1280 \
--form height=720 \
--form format=jpeg \
--form quality=85 \
--form optimizeForSpeed=true \
-o my.jpeg

Viewport & Layout

The viewport defines the image. Unlike PDFs which paginate content, screenshots capture the browser's viewport exactly as rendered.

  • Resolution: Ensure you set the width and height form fields to match your desired target device (e.g., 1920x1080 for desktop, 375x812 for mobile).
  • If your screenshot's content is repeated and clipped, consider setting the skipNetworkIdleEvent form field to false (see issue #1065).
tip

If you are simulating a mobile device, remember to also set the userAgent to match a mobile browser, or the site might serve the desktop version.

Emulated Media Features

You can simulate specific browser conditions by overriding CSS media features. This is particularly useful for forcing "Dark Mode" or testing layouts with reduced motion.

The emulatedMediaFeatures field expects a JSON array of objects, where each object contains a name and a value.

Common Media Features:

Feature NameCommon ValuesDescription
prefers-color-schemelight, darkEmulates the user's OS color theme preference.
prefers-reduced-motionno-preference, reduceEmulates the setting to minimize non-essential motion.
color-gamutsrgb, p3, rec2020Emulates the approximate range of colors supported by the output device.
forced-colorsnone, activeEmulates "High Contrast" modes where the browser restricts colors.
Form Fields
emulatedMediaFeaturesjson
A JSON array of objects to override CSS media features (e.g., prefers-color-scheme).
Default:None
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form emulatedMediaFeatures='[{"name": "prefers-color-scheme", "value": "dark"}, {"name": "prefers-reduced-motion", "value": "reduce"}]' \
-o my.jpeg

Image Format

Choosing the right output format impacts quality and file size:

  • PNG: Best for screenshots containing text, UI elements, or flat colors. It is lossless, ensuring text remains sharp.
  • JPEG: Best for screenshots containing photographs or complex gradients. Use the quality form field to balance file size against visual fidelity (default is 100).
  • WebP: Offers a modern balance of compression and quality, usually superior to JPEG.

JavaScript & Dynamic Content

Chromium captures what is currently visible. If the page relies on JavaScript to render data, charts, or external content, the conversion might trigger before the rendering is complete, resulting in blank or incomplete sections.

Cheatsheets

If the content is generated dynamically:

  1. Use the waitDelay form field to add a fixed pause before conversion.
  2. For more precision, use the waitForExpression form field to trigger the conversion only when a specific JavaScript condition (e.g., window.status === 'ready') is met.

Wait Delay

Use this as a fallback when you cannot modify the target page's code. It forces Gotenberg to wait for a fixed duration before rendering, giving JavaScript time to finish execution.

Reliability Note

This method is "brute force". If the page loads faster, time is wasted. If it loads slower, the image will be incomplete. Use explicit waits (Expression or Selector) whenever possible.

cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form waitDelay=5s \
-o my.jpeg

Wait For Expression

This is the most robust method for synchronization. It pauses the conversion process until a specific JavaScript expression evaluates to true within the page context. This ensures the image is generated exactly when your data is ready.

Example: Client-side logic

// Inside your HTML page.
window.status = "loading";

fetchData().then(() => {
renderCharts();
// Signal to Gotenberg that the page is ready.
window.status = "ready";
});
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
-- form 'waitForExpression=window.status === '\''ready'\''' \
-o my.jpeg

Wait For Selector

Ideally suited for Single Page Applications (SPAs) or frameworks like React/Vue. This method delays the conversion until a specific HTML element - identified by a CSS selector - appears in the DOM.

Example: Dynamic Element Injection

// Inside your HTML page.
await heavyCalculation();

const completionMarker = document.createElement("div");
completionMarker.id = "app-ready"; // The selector we wait for.
document.body.appendChild(completionMarker);
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form 'waitForSelector=#app-ready' \
-o my.jpeg

HTTP & Networking

Cookies

A JSON array of cookie objects for authentication or session state.

KeyDescriptionDefault
nameThe name of the cookie.Required
valueThe value of the cookie.Required
domainThe domain the cookie applies to (e.g., example.com).Required
pathThe URL path the cookie applies to.None
secureIf true, the cookie is only sent over HTTPS.None
httpOnlyIf true, the cookie is inaccessible to JavaScript (document.cookie).None
sameSiteControls cross-site behavior. Values: "Strict", "Lax", "None".None

Cookies expire when the request reaches its time limit. For strict isolation between conversions, configure the API to clear the cookie jar after every request. See API Configuration.

Form Fields
cookiesjson
Array of cookies to add to the request.
Default:None
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form 'cookies=[{"name":"yummy_cookie","value":"choco","domain":"theyummycookie.com"}]' \
-o my.jpeg

HTTP Headers

A JSON object of headers sent with every browser request (including images, stylesheets, scripts).

To restrict a header to specific URLs, append ;scope= with a regex. The scope token is stripped before sending.

Example: "X-Internal-Token": "secret-123;scope=.*\\.internal\\.api"

  • https://data.internal.api/v1 → header sent
  • https://google.com/fonts → header not sent
Form Fields
extraHttpHeadersjson
Custom HTTP headers for the request.
Default:None
userAgentstring
Overrides the default User-Agent header.
Default:None
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form 'userAgent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)"' \
--form-string 'extraHttpHeaders={"X-Header":"value","X-Scoped-Header":"value;scope=https?:\/\/([a-zA-Z0-9-]+\.)*domain\.com\/.*"}' \
-o my.jpeg

Invalid HTTP Status Codes

Return a 409 Conflict if the main page or its resources return specific HTTP status codes (JSON array of integers).

FieldDescription
failOnHttpStatusCodesFails if the main page URL returns a matching code.
failOnResourceHttpStatusCodesFails if any asset (image, CSS, script) returns a matching code.

Status Code Ranges

Ranges use the X99 notation:

  • 499 matches every code from 400 to 499.
  • 599 matches every code from 500 to 599.

Domain Exclusions

Exclude third-party assets (analytics, tracking scripts) from status code checks with ignoreResourceHttpStatusDomains. Matches if the hostname equals or is a subdomain of the entry. Values are normalized (trimmed, lowercased, port/scheme stripped):

  • example.com
  • *.example.com or .example.com
  • example.com:443 (port is ignored)
  • https://example.com/path (scheme/path are ignored)
Form Fields
failOnHttpStatusCodesjson
Returns a 409 Conflict if Gotenberg receives these codes for the main URL.
Default:[499,599]
failOnResourceHttpStatusCodesjson
Returns a 409 Conflict if Gotenberg receives these codes for at least one resource (images/css/etc.).
Default:None
ignoreResourceHttpStatusDomainsjson
Excludes resources from failOnResourceHttpStatusCodes checks based on their hostname.
Default:None
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form 'failOnHttpStatusCodes=[499,599]' \
--form 'failOnResourceHttpStatusCodes=[499,599]' \
--form 'ignoreResourceHttpStatusDomains=["sentry-cdn.com","analytics.example.com"]' \
-o my.jpeg
The main page or one of its resources returned an invalid status code.
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: Invalid HTTP status code from the main page: 400: Bad Request

Network Errors

If the browser hits a critical network error on the main page, the API returns 400 Bad Request:

  • net::ERR_CONNECTION_CLOSED
  • net::ERR_CONNECTION_RESET
  • net::ERR_CONNECTION_REFUSED
  • net::ERR_CONNECTION_ABORTED
  • net::ERR_CONNECTION_FAILED
  • net::ERR_NAME_NOT_RESOLVED
  • net::ERR_INTERNET_DISCONNECTED
  • net::ERR_ADDRESS_UNREACHABLE
  • net::ERR_BLOCKED_BY_CLIENT
  • net::ERR_BLOCKED_BY_RESPONSE
  • net::ERR_FILE_NOT_FOUND
  • net::ERR_HTTP2_PROTOCOL_ERROR

By default, Chromium does not wait for network activity to settle before converting. Two form fields control this behavior:

  • skipNetworkIdleEvent set to false: waits until zero open connections persist for 500ms. Strictest option, but pages with long-polling or analytics connections may never reach this state.
  • skipNetworkAlmostIdleEvent set to false: waits until at most 2 open connections persist for 500ms. A practical middle ground for pages that keep a background connection alive (WebSockets, analytics pings, heartbeat polls).

Both default to true (no wait). If both are set to false, both events must fire before conversion proceeds.

A broken image or stylesheet won't fail the conversion on its own. The PDF is generated with missing assets. Set failOnResourceLoadingFailed to true to fail on any resource network error.

Form Fields
skipNetworkIdleEventboolean
Does not wait for Chromium network to be idle (0 open connections for 500ms).
Default:true
skipNetworkAlmostIdleEventboolean
Does not wait for Chromium network to be almost idle (at most 2 open connections for 500ms).
Default:true
failOnResourceLoadingFailedboolean
Returns a 400 Bad Request if assets (images/css/etc.) fail to load due to network errors.
Default:false
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form skipNetworkIdleEvent=false \
--form skipNetworkAlmostIdleEvent=false \
--form failOnResourceLoadingFailed=true \
-o my.jpeg

Console

Form Fields
failOnConsoleExceptionsboolean
Returns a 409 Conflict response if there are exceptions in the Chromium console.
Default:false
cURL
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form failOnConsoleExceptions=true \
-o my.jpeg
Exceptions in the Chromium console.
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body:

Chromium console exceptions:

exception "Uncaught" (17:10): Error: Exception 1
at file:///tmp/db09d2c8-31e3-4058-9923-c2705350f2b3/index.html:18:11;
exception "Uncaught" (20:10): Error: Exception 2
at file:///tmp/db09d2c8-31e3-4058-9923-c2705350f2b3/index.html:21:11:
Sponsors
TheCodingMachinepdfmePdfBolt
Powered by
DockerJetBrains