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.
- Use document-scoped, short-lived authentication; headers and fields remain visible in Assembly Status, so protect the Assembly ID and status 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",
"acl": "bucket-default",
"path": "inv/1043/${assembly.id}.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
A document can arrive half-empty when a chart or font is not ready at capture time. wait_until selects a browser load state and defaults to networkidle; its other values are domcontentloaded, load, and commit. Network idleness does not prove that application data, an animation, or a delayed chart has finished rendering. Selecting an earlier state can shorten the wait, but cannot fix content that is still missing under the default.
delay adds a fixed pause after the selected load state. For late-rendering content, first make the page render from ready data where possible, then test a bounded delay when the page cannot expose readiness through its loading behavior. That pause increases latency on every render, including those that did not need it. Verify the actual PDF rather than treating either network idleness or a fixed wait as a content-completeness guarantee.
wait_until
Defaults to networkidle. A browser load state does not guarantee application content is ready.
delay
Adds a fixed pause after the selected load state on every render. Test it for content that finishes later.
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.
Use a short-lived, preferably single-use token scoped to one document. Anyone holding the Assembly ID or status URL can retrieve Assembly Status without further authentication, including its fields. The sample passes the token through fields.token, so it is visible there. notification_payload: ["without_params"] removes params, template, and merged_params from notifications; it does not remove fields or redact Status. Never use a reusable account token here. Keep IDs, status URLs, and raw status responses out of public logs and client responses, redact tokens in application logs, and expire document access independently of Status retention.
{
"notification_payload": ["without_params"],
"steps": {
"rendered": {
"robot": "/html/convert",
"url": "https://example.com/reports/q3",
"format": "pdf",
"wait_until": "networkidle",
"headers": {
"Authorization": "Bearer ${fields.token}"
}
}
}
}headers
Carries a short-lived, document-scoped token outside the URL. Treat Assembly parameters and logs as sensitive.
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. Use /document/merge with document_1, document_2, and subsequent as aliases to set the order, and bundle_steps: true to collect the parts. This avoids relying on input filenames or giving a second service 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": {
"steps": [
{ "name": "cover", "as": "document_1" },
{ "name": "detail", "as": "document_2" }
],
"bundle_steps": true
},
"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; use explicit as aliases to control 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 finalization
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 defaults to networkidle and selects a browser load state. It does not guarantee that delayed charts, fonts, or application data are ready.
- The delay parameter adds a fixed pause after the load state is reached. Test a bounded delay for late-rendering content, and account for the added latency on every render.
- 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 a private bucket with the identifiers that produced it. Use bucket-default for S3 buckets with ACLs disabled and enforce access through the bucket policy.
- 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 page may not have finished rendering its application content. The default wait_until is already networkidle, which does not guarantee that delayed charts or fonts are ready. Render from ready data where possible, or test a bounded delay after the load state. Inspect the PDF to confirm the missing content appears; the fixed pause increases latency 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. Assign document_1, document_2, and subsequent as aliases and enable bundle_steps: true. Without explicit aliases, the Robot sorts by filename, not by the order of Step names in the array.