Key takeaways
- Set the pdf format on /html/convert; the other formats produce screenshots instead.
- Control when the snapshot is taken with wait_until and, only when necessary, delay.
- Send authentication through headers rather than embedding credentials in the URL.
Most PDF requirements start as a page that already renders correctly in a browser. Rendering that page server-side is usually cheaper than maintaining a second layout in a PDF library, provided the timing and the inputs are controlled.
What matters most
- Render from a stable, versioned template URL so a design change cannot alter an issued document.
- Merge multi-part documents with /document/merge instead of concatenating PDFs yourself.
Render the page you already have
Most PDF requirements begin as a page that already renders correctly in a browser: an invoice, a statement, a report. Maintaining a second layout in a PDF library duplicates that work and guarantees the two will drift. /html/convert renders the page with a headless browser and returns the result, and setting format: "pdf" is what distinguishes a document from a screenshot. The same Robot produces jpeg, jpg, and png, which are images of the page rather than paginated documents.
The Robot accepts either a url to render or an uploaded HTML file. Rendering a URL is usually the better choice for documents that already exist as pages, because it keeps one source of truth. Uploading HTML suits documents assembled on the fly, where no stable URL exists.
{
"steps": {
"rendered": {
"robot": "/html/convert",
"url": "https://example.com/inv/1043?v=3",
"format": "pdf",
"wait_until": "networkidle"
},
"exported": {
"use": "rendered",
"robot": "/s3/store",
"credentials": "my_s3_credentials",
"path": "inv/1043.pdf"
}
}
}format: "pdf"
Produces the document. The other formats capture an image of the page instead.
url or upload
Render an existing page by URL, or upload generated HTML when no stable URL exists.
omit_background
Applies to image output only. Transparency cannot be carried into a PDF.
Control when the snapshot is taken
The most common failure is a document that renders correctly by hand and arrives half-empty from the Robot, because the snapshot was taken before fonts loaded or a chart finished drawing. wait_until maps to the browser load state and is the precise way to express that dependency. Choosing the right load state fixes most timing problems without adding fixed latency.
delay adds a fixed pause afterwards. It is occasionally necessary for animations or third-party widgets that report ready before they are, but it costs that pause on every single render, including the ones that did not need it. Reach for a more specific wait_until first, and treat delay as the fallback rather than the default.
wait_until
Expresses the actual dependency on the browser load state. Prefer it.
delay
A fixed pause charged on every render. Use only when a load state cannot express the wait.
Print stylesheet
Verify the page in a browser print preview before rendering it server-side.
Reach protected pages without leaking credentials
Because rendering runs a real browser, the page has to be reachable from Transloadit. Documents usually live behind authentication, which leaves two workable options. The headers parameter passes authentication with the request, which suits token-based access. Alternatively, issue a short-lived signed URL that grants access to exactly one document for a short window.
Putting credentials in the query string of the rendered URL is the option to avoid. Those URLs end up in logs and in the stored record of what was rendered, and unlike a header they are trivially reusable if the record is ever exposed.
{
"steps": {
"rendered": {
"robot": "/html/convert",
"url": "https://example.com/reports/q3",
"format": "pdf",
"wait_until": "networkidle",
"headers": [
"Authorization: Bearer ${fields.token}"
]
}
}
}headers
Carries tokens with the request rather than in the URL, so they stay out of logs.
Signed one-time URLs
Grant access to a single document for a short window when header auth is not available.
Never in the query string
Credentials placed there are recorded wherever the rendered URL is stored.
Make a reissued document identical to the original
An invoice is a legal record, and the version a customer receives in March should still render identically in November. Two habits achieve that. Render from a versioned template URL so that a later design change cannot alter a document that has already been issued, and store the resulting file rather than regenerating it on demand.
Documents assembled from several parts are worth handling explicitly. /document/merge combines rendered pages into one file inside the same Assembly, which keeps ordering deterministic and avoids a second service that has to be given access to the parts.
{
"steps": {
"cover": {
"robot": "/html/convert",
"url": "https://example.com/stmt/cover?v=3",
"format": "pdf",
"wait_until": "networkidle"
},
"detail": {
"robot": "/html/convert",
"url": "https://example.com/stmt/detail?v=3",
"format": "pdf",
"wait_until": "networkidle"
},
"statement": {
"use": ["cover", "detail"],
"robot": "/document/merge"
}
}
}Version the template
A design change should produce new documents, not retroactively change issued ones.
Store, do not regenerate
Keep the produced file so a reissue is a copy rather than a fresh render.
/document/merge
Combines multi-part documents in one Assembly with deterministic ordering.
Keep the cost of rendering predictable
A page render is more expensive than a format conversion, because it starts a browser, fetches subresources, and waits for the page to settle. That cost is fine for a document a customer asked for, and wasteful when the same statement is rendered afresh every time somebody opens a list view. The usual fix is to render once at the moment the document becomes final, then serve the stored file.
Bulk generation deserves separate treatment. A month-end run that produces thousands of statements should not compete with the render a customer is waiting on, and a fixed delay applied across such a batch multiplies into real time and money. Measuring cost per issued document, rather than per Assembly, tends to expose these patterns quickly.
Render at finalisation
Produce the file when the document becomes final, not on every view.
Separate bulk runs
Keep month-end batches away from renders a person is waiting for.
Audit fixed delays
A one-second pause is invisible once and expensive across ten thousand documents.
Verify the document before a customer sees it
A render can succeed and still be wrong. The Robot returns a valid PDF whether or not the chart drew, so a check that only asks whether a file was produced will not catch a blank page. Cheap assertions catch most of it: a plausible byte size, an expected page count, and the presence of a known string such as the document number.
Rendering to png alongside the PDF during development gives a fast visual check that is easy to eyeball in review, and comparing a fresh render against a stored reference image catches layout regressions that a byte-size check cannot. Neither belongs in the production path, but both are worth having in the pipeline that ships template changes.
Assert content, not existence
Check page count and a known identifier rather than only that a file exists.
Image renders for review
A png of the same page makes template changes reviewable at a glance.
Compare against a reference
Visual comparison catches layout regressions that size checks will miss.
Technical details worth knowing
- The format parameter accepts jpeg, jpg, pdf, and png. Only pdf produces a document; the rest capture an image of the page.
- The omit_background parameter applies to image output and has no effect when format is pdf, so transparency cannot be carried into the document.
- The wait_until parameter maps to the underlying browser load state, which is the reliable way to wait for fonts, charts, and late-loading data before the snapshot is taken.
- The delay parameter adds a fixed pause after the load state is reached. It is a blunt instrument that raises cost and latency on every render, so prefer a more specific wait_until where possible.
- A rendered invoice is a legal record. Rendering from an immutable template URL, and storing the resulting file rather than regenerating it on demand, keeps a reissued copy identical to the original.
- Because rendering runs a real browser, the page must be reachable from Transloadit. Pages behind a session cookie need either a signed one-time URL or the credentials passed through the headers parameter.
A practical approach
- 1
Build the document as a normal page with a print stylesheet and verify it in a browser first.
- 2
Render it with /html/convert using the pdf format and an explicit wait_until.
- 3
Store the result in your own bucket with the identifiers that produced it.
- 4
Merge supporting pages into one file with /document/merge when the document has several parts.
When Transloadit is useful
Use /html/convert with the pdf format when the document already exists as a web page or can be rendered as one. Point it at a url, or upload HTML and let the Robot render the uploaded file. Combine it with /document/merge when several pages belong in one file.
Architecture boundary
/html/convert renders a page with a headless browser, so it produces a paginated visual copy rather than a tagged, accessible PDF. Documents that need selectable structure, form fields, or long-term archival formats such as PDF/A should be produced by a dedicated document generator.
Frequently asked questions
Why is my PDF missing charts or fonts?
The snapshot was almost certainly taken before those finished loading. Set wait_until to a load state that covers the dependency. Add delay only if a load state cannot express it, remembering that the pause is charged on every render.
Can I produce a PDF with a transparent background?
No. omit_background affects image output, and has no effect when format is pdf. If transparency is required, render to png instead and place that image into a document.
How do I render a page that requires a login?
Pass authentication through the headers parameter, or issue a short-lived signed URL scoped to the single document. Avoid putting credentials in the query string, because the rendered URL is recorded wherever the render is logged.
Is the output an accessible, tagged PDF?
No. A headless browser produces a paginated visual copy, not a tagged document with a reading order, form fields, or PDF/A conformance. Requirements of that kind need a dedicated document generator rather than a page render.
How do I combine several rendered pages into one file?
Render each part and pass the results to /document/merge in the same Assembly. Keeping the merge inside the Assembly makes the ordering deterministic and avoids granting another service access to the individual parts.