Screenshot HTML
Converts uploaded HTML content into an image using Headless Chromium.
Unlike the URL route, this endpoint renders files provided directly in the request body. It requires an index.html file to serve as the entry point. You may also include additional assets (CSS, images, fonts) in the multipart/form-data request, which can be referenced in your HTML using relative paths.
You can configure the Chromium module behavior. See the Chromium module configuration for details.
Basics
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
-o my.jpeg
- 200 OK
- 400 Bad Request
- 503 Service Unavailable
Content-Disposition: attachment; filename={output-filename.ext}
Content-Type: {content-type}
Content-Length: {content-length}
Gotenberg-Trace: {trace}
Body: {output-file}
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: {error}
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: Service Unavailable
Assets
You can send local files - such as images, fonts, and stylesheets - alongside your HTML to create a rich document.
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
When you upload files to Gotenberg, they are stored in a single temporary directory. Consequently, your HTML must reference these assets by filename only, regardless of their original folder structure on your machine.
Do not use absolute paths (/img.png) or subdirectories (./assets/img.png).
✅ Correct Reference
Since index.html and logo.png sit side-by-side in the container:
<!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.
<!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>
If your HTML references remote URLs (e.g., CDNs, Google Fonts, localhost):
- DNS & Firewalls: Ensure the container can resolve the domain name and that no firewalls are blocking the connection from the container's IP.
- Localhost: If you are trying to convert a page running on the host machine (e.g.,
http://localhost:8080), you cannot uselocalhostinside the container. You must use the host's network IP or a special Docker DNS name likehost.docker.internal(depending on your OS and Docker version).
For small assets, embedding them directly as Base64 data URIs eliminates network dependencies entirely.
Avoid using the HTML <base> element. It alters how relative paths are resolved and often breaks the link between your HTML and the uploaded local assets.
Rendering Behavior
800600falsepng100falsefalseprintNoneNoneNonecurl \
--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
widthandheightform fields to match your desired target device (e.g.,1920x1080for desktop,375x812for mobile). - If your screenshot's content is repeated and clipped, consider setting the
skipNetworkIdleEventform field to false (see issue #1065).
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.
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
qualityform field to balance file size against visual fidelity (default is100). - 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.
If the content is generated dynamically:
- Use the
waitDelayform field to add a fixed pause before conversion. - For more precision, use the
waitForExpressionform 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.
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 \
--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 \
--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 \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form 'waitForSelector=#app-ready' \
-o my.jpeg
HTTP & Networking
NoneNoneNonetrue[499,599]NoneNonefalsecurl \
--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 skipNetworkIdleEvent=false \
--form failOnResourceLoadingFailed=true \
-o my.jpeg
Cookies
The cookies form field accepts a JSON-formatted array of cookie objects. It allows you to authenticate requests or maintain session state during the conversion process.
Cookie Object Schema
| Key | Description | Default |
|---|---|---|
name | The name of the cookie. | Required |
value | The value of the cookie. | Required |
domain | The domain the cookie applies to (e.g., example.com). | Required |
path | The URL path the cookie applies to. | None |
secure | If true, the cookie is only sent over HTTPS. | None |
httpOnly | If true, the cookie is inaccessible to JavaScript (document.cookie). | None |
sameSite | Controls cross-site behavior. Values: "Strict", "Lax", "None". | None |
Cookies are transient and automatically expire when the request reaches its time limit.
To ensure strict isolation between conversions (preventing data leakage), you can configure the API to explicitly clear the cookie jar after every request. See API Configuration for details.
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
The extraHttpHeaders form field accepts a JSON-formatted object representing the HTTP headers to send with every request.
Schema
This is a key-value map where:
- Key: The header name (e.g.,
Authorization,X-Custom-Header). - Value: The header value (string).
By default, headers are sent with every request made by the browser (including images, stylesheets, and scripts).
To restrict a header to specific URLs, append a ;scope= token containing a Regular Expression. Gotenberg will process this token and only send the header if the target URL matches the regex.
Example:
"X-Internal-Token": "secret-123;scope=.*\\.internal\\.api"
- Matches:
https://data.internal.api/v1(Header sent) - Ignores:
https://google.com/fonts(Header NOT sent)
Note: The scope token is stripped before the header is sent to the server.
curl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--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
You can configure Gotenberg to return a 409 Conflict error if the main page or its resources return specific HTTP status codes.
These fields accept a JSON-formatted array of integers.
| Field | Description |
|---|---|
failOnHttpStatusCodes | Fails if the main page URL returns a matching code. |
failOnResourceHttpStatusCodes | Fails if any asset (image, CSS, script) returns a matching code. |
Status Code Ranges
You can define ranges using the X99 notation:
499matches every code from 400 to 499.599matches every code from 500 to 599.
Domain Exclusions
Use ignoreResourceHttpStatusDomains to prevent failures for specific third-party assets (e.g., analytics or tracking scripts that might fail without affecting the PDF).
Matching Rules: Gotenberg matches if the asset's hostname equals or is a subdomain of the entry.
Input values are automatically normalized (trimmed, lowercased, port/scheme removed):
example.com*.example.comor.example.comexample.com:443(port is ignored)https://example.com/path(scheme/path are ignored)
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
- 409 Conflict
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: Invalid HTTP status code from the main page: 400: Bad Request
Network Errors
Gotenberg automatically validates the connection to the main page URL. If the browser encounters any of the following critical network errors, the API immediately returns 400 Bad Request to indicate the page could not be reached.
Critical Error List
net::ERR_CONNECTION_CLOSEDnet::ERR_CONNECTION_RESETnet::ERR_CONNECTION_REFUSEDnet::ERR_CONNECTION_ABORTEDnet::ERR_CONNECTION_FAILEDnet::ERR_NAME_NOT_RESOLVEDnet::ERR_INTERNET_DISCONNECTEDnet::ERR_ADDRESS_UNREACHABLEnet::ERR_BLOCKED_BY_CLIENTnet::ERR_BLOCKED_BY_RESPONSEnet::ERR_FILE_NOT_FOUNDnet::ERR_HTTP2_PROTOCOL_ERROR
By default, if an image or stylesheet fails to load (e.g., a broken 404 image link), the PDF is still generated with a missing asset icon.
To force the conversion to fail completely when any resource encounters a network error, set the failOnResourceLoadingFailed form field to true.
Console
falsecurl \
--request POST http://localhost:3000/forms/chromium/screenshot/html \
--form files=@/path/to/index.html \
--form failOnConsoleExceptions=true \
-o my.jpeg
- 409 Conflict
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:
