Use case
HTML to PDF API
Send HTML, get a PDF: no template required. Chromium renders it with full CSS support, and the settings object controls the paper, the margins, the header and the footer. When the same HTML keeps coming with other values, move it into a template and send only the data.
Free plan with 100 units a month, no credit card
Real browser rendering
Flexbox, grid, web fonts, @page rules and print stylesheets work as in Chrome; no PDF library quirks.
Headers and footers
Header and footer templates with the page number and the page count on every page, and a margin per side.
Any paper
A4, Letter and the other common formats, landscape, or a width and a height of your own, such as a label of 100 × 150 mm.
Tailwind and web fonts
settings.tailwind compiles the Tailwind classes the document uses, and fonts load from a link tag as in a browser.
Safe by design
Sub-resources load only over https from public hosts; private networks and metadata endpoints are unreachable.
Test keys
Test keys render for free (with a small watermark on the Free plan), so integration and CI never touch your quota.
Convert HTML to PDF with one request
curl -X POST "https://api-eu.formfeed.dev/v1/renders?output=binary" \
-H "Authorization: Bearer $FORMFEED_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Hello</h1><p>From HTML to PDF.</p>", "settings": {"paper": {"format": "A4"}, "margin": {"top": "20mm", "bottom": "20mm"}}}' \
-o hello.pdfFull request and response shapes in the Docs.
What you can send
The document goes in as html, up to 5 MB. Stylesheets, fonts and images are inline or load over HTTPS from public addresses, and the page is printed when it has loaded, none of its requests is still running and the fonts are ready. The command above asks for the file itself with ?output=binary; without it the answer is JSON with a signed download link, the page count and the units.
With engine set to jinja2, liquid or handlebars, the HTML is a template for the data you send along, with every helper for money, dates and codes. Without it the HTML is printed as it is.
{
"html": "<h1>Invoice {{ number }}</h1><p>Total: {{ total | money }}</p>",
"engine": "liquid",
"data": { "number": "2026-0042", "total": 3760.4 },
"locale": "de-DE"
}Paper, margins, header and footer
What a print dialog would ask is in settings: the paper by name or by width and height, portrait or landscape, a margin per side, and the HTML of a header and a footer that repeat on every page.
{
"html": "<h1>Quarterly report</h1> …",
"settings": {
"paper": { "format": "A4", "landscape": true },
"margin": { "top": "25mm", "right": "15mm", "bottom": "20mm", "left": "15mm" },
"footer": {
"height": "12mm",
"html": "<div style=\"width:100%;text-align:center;font-size:8px\">Page <span class=\"pageNumber\"></span> of <span class=\"totalPages\"></span></div>"
}
}
}Chromium prints header and footer outside the page, so they carry their own inline styles, and pageNumber and totalPages are filled in on each page. preferCssPageSize lets an @page rule of your CSS decide the size instead.
Pages that finish late
JavaScript runs, so a page that draws a chart or loads data is printed when it is done: waitFor.selector waits for an element, and waitFor.delayMs adds a pause of up to ten seconds. Loading the page and everything it loads may take up to 30 seconds. A request the caller should not wait for goes with "mode": "async" and reports through a webhook.
When the same HTML keeps coming: a template
Sending the whole document works for one-off pages. For an invoice or a report that comes a thousand times with other values, store the HTML once as a template: the request shrinks to the data, the layout has versions, the editor shows the pages before anything is sent, and a change to the wording is no deployment. An HTML file you already have is a template as it is.
A web page, an image, an office file
Pass url instead of html to print a public page: URL to PDF. Ask for png, jpg or webp with output, and the same HTML comes back as an image. Word, Excel and PowerPoint files convert to PDF through POST /v1/pdf/convert from the Starter plan: office conversion.
What a PDF costs
A PDF of up to five pages is one unit, and every further five pages add one; an image is one unit. The Free plan has 100 units a month and asks for no credit card. Starter has 3,000 units for €19 a month. Renders with a test key cost nothing, and a render that fails is never counted. Every plan is on the pricing page.
FAQ
- What about URLs?
- Pass
urlinstead ofhtmlto print a public page. URL to PDF says what the page has to be and how a screenshot differs from a print. - How large can the HTML be?
- Up to 5 MB per request; images and fonts are fetched by the renderer and cached.
- Which CSS works in the PDF?
- What Chromium prints: flexbox, grid,
@page,break-inside, web fonts and print stylesheets. Page breaks in tables shows with rendered files what holds across pages and what does not. - Does JavaScript run?
- Yes. The page is printed when it has loaded, the network is idle and the fonts are ready, and
waitForadds conditions for pages that finish later. - Is my HTML stored?
- The file is kept for the retention of your plan, or shorter with
expires_in. The request itself is not kept after the render, unless an owner or admin switches on Keep render requests for the workspace: from the Starter plan, for 24 hours, and from Pro for up to 7 days. - Can I convert Word or Excel files too?
- Yes, on Starter and above:
POST /v1/pdf/converttakes DOCX, XLSX, PPTX, OpenDocument, RTF and older Office files up to 20 MB and returns a PDF render; the upload is not stored. See office conversion.