Convert URL to PDF
Converts a target URL to PDF using Headless Chromium.
This route works by simulating a standard browser navigation: it connects to the URL, executes the necessary JavaScript, loads assets (CSS, images, fonts), and captures the final state of the DOM as a PDF. It supports Single Page Applications (SPAs) and dynamic content rendering.
You can configure the Chromium module behavior. See the Chromium module configuration for details.
Basics
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
-o my.pdf
- 200 OK
- 400 Bad Request
- 403 Forbidden
- 503 Service Unavailable
Content-Disposition: attachment; filename={output-filename.pdf}
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: Forbidden
Content-Type: text/plain; charset=UTF-8
Gotenberg-Trace: {trace}
Body: Service Unavailable
Rendering Behavior
8.5110.390.390.390.39false1.0falsefalsefalsefalseprintNoneNoneNonecurl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form paperWidth=8.27 \
--form paperHeight=11.7 \
--form marginTop=1 \
--form marginBottom=1 \
--form marginLeft=1 \
--form marginRight=1 \
--form landscape=true \
--form scale=2.0 \
--form printBackground=true \
--form omitBackground=true \
--form singlePage=true \
--form preferCssPageSize=false \
--form emulatedMediaType=screen \
-o my.pdf
Standard Paper Sizes
The following standard paper sizes are in inches (width x height), ordered from smallest to largest:
| Format | Dimensions | Format (US) | Dimensions |
|---|---|---|---|
| A6 | 4.13 x 5.83 | Letter | 8.5 x 11 (Default) |
| A5 | 5.83 x 8.27 | Legal | 8.5 x 14 |
| A4 | 8.27 x 11.7 | Tabloid | 11 x 17 |
| A3 | 11.7 x 16.54 | Ledger | 17 x 11 |
| A2 | 16.54 x 23.4 | ||
| A1 | 23.4 x 33.1 | ||
| A0 | 33.1 x 46.8 |
Single Page
If singlePage is set to true, it automatically overrides the values from paperHeight and nativePageRanges.
CSS & Print Media
Chromium uses the print media type. By default, browsers optimize pages for printing by removing background colors/images and adjusting the layout to save ink.
If your generated PDF looks different from what you see in a browser viewport, it is likely because print media styles are being applied or background graphics are disabled.
- Use
@media printin your CSS to define specific styles for the PDF (e.g., hiding navigation bars or buttons). - Alternatively, use the
emulatedMediaTypeform field to force the media type toscreen. - To force background colors and images to appear, enable the
printBackgroundform field in your request. - You can control paper size and margins via CSS using the
@pagerule. Note that for Gotenberg to respect these rules, you must enable thepreferCssPageSizeform field.
Background Logic
The final background depends on printBackground, omitBackground, and your document's CSS:
| Print Background? | Omit Background? | HTML CSS has BG? | Resulting Output |
|---|---|---|---|
false | (Any) | (Any) | No Background |
true | (Any) | Yes | Uses HTML CSS Background |
true | true | No | Transparent |
true | false | No | White (Default) |
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 PDF will be incomplete. Use explicit waits (Expression or Selector) whenever possible.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form waitDelay=5s \
-o my.pdf
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 PDF 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/convert/url \
--form url=https://my.url \
-- form 'waitForExpression=window.status === '\''ready'\''' \
-o my.pdf
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/convert/url \
--form url=https://my.url \
--form 'waitForSelector=#app-ready' \
-o my.pdf
HTTP & Networking
NoneNoneNonetrue[499,599]NoneNonefalsecurl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form 'userAgent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko)"' \
--form skipNetworkIdleEvent=false \
--form failOnResourceLoadingFailed=true \
-o my.pdf
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/convert/url \
--form url=https://my.url \
--form 'cookies=[{"name":"yummy_cookie","value":"choco","domain":"theyummycookie.com"}]' \
-o my.pdf
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/convert/url \
--form url=https://my.url \
--form-string 'extraHttpHeaders={"X-Header":"value","X-Scoped-Header":"value;scope=https?:\/\/([a-zA-Z0-9-]+\.)*domain\.com\/.*"}' \
-o my.pdf
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/convert/url \
--form url=https://my.url \
--form 'failOnHttpStatusCodes=[499,599]' \
--form 'failOnResourceHttpStatusCodes=[499,599]' \
--form 'ignoreResourceHttpStatusDomains=["sentry-cdn.com","analytics.example.com"]' \
-o my.pdf
- 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/convert/url \
--form url=https://my.url \
--form failOnConsoleExceptions=true \
-o my.pdf
- 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:
Header & Footer
You can inject a custom header and footer into every page of the generated PDF.
These must be provided as separate, complete HTML files (header.html and footer.html).
This feature uses the native Chromium printing engine. It operates in a separate context from your main document, which means:
- Styles are isolated: Your main page's CSS does not apply here.
- No JavaScript: Scripts inside headers/footers will not execute.
- No External Requests: You cannot link to external images, fonts, or stylesheets.
NoneNonecurl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form files=@/path/to/header.html \
--form files=@/path/to/footer.html \
-o my.pdf
HTML Structure
Each file must be a full HTML document with its own <html>, <head>, and <body> tags.
<html>
<head>
<style>
body {
/* Recommended: Use a larger font-size than normal */
font-size: 16px;
/* Recommended: Use margins to align with the page edge */
margin: 0 20px;
/* Required for background colors to show */
-webkit-print-color-adjust: exact;
}
</style>
</head>
<body>
<p>
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
</p>
</body>
</html>
Dynamic Content Injection
Chromium automatically injects values into elements with specific class names:
| Class Name | Injected Value |
|---|---|
date | The formatted print date. |
title | The document title. |
url | The document location. |
pageNumber | The current page number. |
totalPages | The total number of pages. |
Styling & Asset Limitations
The Chromium header/footer engine operates in a restricted environment. Follow these rules to ensure correct rendering:
| Component | Limitation & Solution |
|---|---|
| Images | Must be Base64 encoded inline (e.g., <img src="data:image/png;base64,...">). External URLs (HTTP) will not load. |
| Fonts | Only fonts installed in the Docker image are available. See Fonts Configuration. |
| Colors | To force background/text colors to print, you must explicitly add -webkit-print-color-adjust: exact; to your CSS. |
| Margins | Content taller than the marginTop / marginBottom form fields will be clipped. |
| CSS | footer.html styles may override header.html styles. Use unique class names to prevent conflicts. |
Do not attempt to load external assets (CSS, images, scripts) in headers or footers. They will likely time out, slowing down the rendering process significantly or causing the request to fail entirely.
Structure & Metadata
falseNonefalseNoneDocument Outline (Chromium)
Use the generateDocumentOutline form field to automatically create a PDF bookmark pane (sidebar) based on your HTML headings (<h1> to <h6>).
- Process: Happens during the HTML-to-PDF conversion.
- Requirement: Your HTML must use proper heading tags to generate the hierarchy.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form generateDocumentOutline=true \
-o my.pdf
Metadata (PDF Engines)
Use the metadata form field to inject XMP metadata into the generated PDF. This allows you to set properties like
Author, Title, Copyright, and Keywords by passing a JSON-formatted object.
Not all metadata tags are writable. Gotenberg relies on ExifTool for this operation. See the XMP Tag Name documentation for a list of potential keys.
Writing metadata involves modifying the PDF structure and usually breaks PDF/A compliance.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form 'metadata={"Author":"Julien Neuhart","Copyright":"Julien Neuhart","CreationDate":"2006-09-18T16:27:50-04:00","Creator":"Gotenberg","Keywords":["first","second"],"Marked":true,"ModDate":"2006-09-18T16:27:50-04:00","PDFVersion":1.7,"Producer":"Gotenberg","Subject":"Sample","Title":"Sample","Trapped":"Unknown"}' \
-o my.pdf
Attachments (PDF Engines)
Use the embeds form field to attach external files directly inside the PDF container.
Common Use Case: This is essential for e-invoicing standards like ZUGFeRD / Factur-X, which require a human-readable PDF to carry a machine-readable XML invoice as an attachment.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form embeds=@invoice.xml \
--form embeds=@logo.png \
-o my.pdf
Flatten (PDF Engines)
Use the flatten form field to make the PDF non-interactive.
This process merges all interactive form fields (text inputs, checkboxes, etc.) directly into the page content. The resulting PDF cannot be modified by the end-user.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form flatten=true \
-o my.pdf
Split & Page Ranges
NoneNoneNonefalseNative Printing (Chromium)
Use the nativePageRanges form field to instruct Chromium which pages to print.
- Process: Happens during the HTML-to-PDF conversion.
- Output: A single PDF file containing only the selected pages.
- Performance: Faster, as unused pages are never rendered.
Unlike word processors, HTML has no intrinsic concept of "pages" until it is printed. To prevent awkward breaks (like a table header being separated from its rows or an image being cut in half), use standard CSS fragmentation properties:
break-inside: avoid;(prevents an element from being split across two pages).break-before: always;orbreak-after: always;(forces a new page).
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form nativePageRanges=1-5 \
-o my.pdf
Post-Processing (PDF Engines)
Use the splitMode, splitSpan, and splitUnify form fields to manipulate the PDF after it has been generated.
- Process: Gotenberg generates the full PDF, then uses a PDF Engine (like pdfcpu) to split or extract pages.
- Output: A single PDF or a ZIP archive containing PDF files.
- Use Case: Creating separate files for every page, or extracting intervals into separate documents.
When splitMode is set to pages, Gotenberg does not validate the splitSpan syntax.
The value is passed directly to the underlying PDF Engine, and the valid syntax depends on which engine you have configured:
| Engine | Syntax Reference |
|---|---|
| pdfcpu (Default) | See pdfcpu /trim documentation |
| QPDF | See QPDF page-ranges documentation |
| PDFtk | See PDFtk cat operation |
Check the PDF Engines Configuration to see which engine is active.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form splitMode=intervals \
--form splitSpan=1 \
-o my.zip
PDF/A & PDF/UA
falseNonefalseNative Accessibility (Chromium)
Use the generateTaggedPdf form field to instruct Chromium to structure the PDF for accessibility (tagging).
- Process: Happens during the initial HTML-to-PDF conversion.
- Performance: High (negligible overhead).
- Result: A logical structure tree (headings, paragraphs) embedded in the PDF, essential for screen readers.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form generateTaggedPdf=true \
-o my.pdf
Post-Processing (PDF Engines)
Use the pdfa and pdfua form fields to convert the result into a standardized PDF format during post-processing.
- Process: Gotenberg generates the result, then re-processes it using LibreOffice (the only engine supporting these standards).
- Performance: Slower (requires a second conversion pass).
- Result: A compliant PDF/A (Archival) or PDF/UA (Universal Accessibility) document.
curl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form userPassword=my_user_password \
--form ownerPassword=my_owner_password \
-o my.pdf
Encryption (PDF Engines)
Secure your PDF by setting passwords that control access and permissions.
- User Password: Required to open and view the PDF.
- Owner Password: Required to modify permissions (e.g., printing, copying text, extracting pages).
The encryption strength (e.g., AES-256) depends on the configured PDF Engine (QPDF, pdfcpu, or PDFtk).
Check the PDF Engines Configuration to see which engine is active.
NoneNonecurl \
--request POST http://localhost:3000/forms/chromium/convert/url \
--form url=https://my.url \
--form userPassword=my_user_password \
--form ownerPassword=my_owner_password \
-o my.pdf
