<style>@page { size: Letter; }thead { display: table-header-group; }</style><h1>Invoice 2026-0042</h1><p>Acme Co. · due Oct 15</p><table> <thead><tr><th>Item</th>… <tr><td>API renders</td>… <tr><td>Support</td>…</table>
PDF
Page 1 of 3
PDF ready
What is an HTML to PDF API?
An HTML to PDF API is a web service that turns HTML and CSS into a PDF
file. You send your markup in an HTTP request; the service loads it in a headless browser,
applies print styles, page size, margins, headers and footers, and returns the finished PDF,
so you never have to run or scale a browser yourself.
Dynamic Document API does exactly that with one endpoint, POST /v1/pdf/from-html. The
facts at a glance:
HTML to PDF API key facts
Endpoint
POST https://api.dynamicdocumentapi.com/v1/pdf/from-html
Input
HTML plus optional <head> content (CSS, fonts, scripts); optional JSON data for {{ variables }}
Output
PDF as a signed URL (default), raw binary or base64
Rendering engine
Engine 2026.4 on Chromium (Chrome 153.0.8010.47), JavaScript on by default
Render time
Median 216 ms, p95 516 ms, p99 667 ms (queue + processing, excluding network)1
Page sizes
13 named sizes (A0–A6, B4, B5, Letter, Legal, Tabloid, Ledger) or any custom size from 0.1 to 200 inches
Delivery
Synchronous by default; asynchronous with webhooks for long jobs
Data residency
Payloads and files stay in the EU
Client libraries
Plain REST and JSON: any HTTP client works. Examples below for cURL, Node.js, Python and PHP
Price
Free: 50 renders/month. Paid from €15/month (billed annually) for 3,000 renders, auto top-ups from €6 per 1,000
Last updated
1. Measured over 1,664 successful renders on engine 2026.4.
Render time is total_ms: time in the queue plus processing in the worker,
excluding API overhead and network transfer. p75 378 ms, p90 478 ms, maximum
2,261 ms.
Convert HTML to PDF in one API request
Every request carries your HTML, optional <head> content, and a
pdf object with the print settings. The example below renders a US Letter page
with 20 mm top and bottom margins and a page-number footer. The cURL and Python
versions ask for the file itself ("delivery": "binary"); the Node.js and PHP
versions use the default and get back JSON with a signed download URL.
curl https://api.dynamicdocumentapi.com/v1/pdf/from-html \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Hello from HTML</h1><p>Rendered by Chromium.</p>",
"head": "<style>body{font-family:system-ui} h1{color:#4751e9}</style>",
"pdf": {
"paper": { "size": "Letter" },
"margin": { "top": "20mm", "bottom": "20mm" },
"footer": { "enabled": true, "center": "Page {{page}} of {{pages}}" }
},
"delivery": "binary"
}' \
--output hello.pdf
# hello.pdf is written to the current directory
The most-used fields. The full reference, including every enum value and limit, is in the
API documentation.
Parameters of POST /v1/pdf/from-html
Field
What it does
html
Required. The markup to render. When data is set, {{ variables }} are filled in first.
head
Contents of <head>: <style>, <link>, <script>, <meta>.
data
JSON values for the variables in html and head.
pdf.paper
A named size (A4, Letter, …) or width and height with units, e.g. "80mm".
pdf.orientation
portrait or landscape.
pdf.margin
top, right, bottom, left as CSS lengths ("20mm", "0.5in").
pdf.scale
Zoom factor from 0.1 to 2.0.
pdf.print_background
Print background colors and images. Default true.
pdf.emulate_media
print (default) or screen CSS media.
pdf.header, pdf.footer
Running header and footer, as HTML or as left/center/right text with {{page}} and {{pages}}.
pdf.wait
When to capture: until = load, networkidle, selector, ready_flag or delay; timeout_ms up to 300,000.
pdf.locale, pdf.timezone
Locale and time zone the page runs in, e.g. en-US and America/New_York.
delivery
url (default, signed link), binary or base64.
mode, webhook
sync (default) or async, with a webhook that fires when the file is ready.
What the rendering engine supports
The engine is Chromium, the same code base as desktop Chrome, pinned to a tested release
(engine 2026.4 runs Chrome 153). If your page prints correctly in Chrome, it renders the
same way here. HTML to PDF is one input of our
complete PDF generation API; the print features below
are shared by all of them.
Modern CSS: flexbox, grid, @page and print media queries
Flexbox, grid, custom properties and @media print work as in current Chrome.
Print media is emulated by default. Set prefer_css_page_size to let
@page rules decide the page size.
Use simple left/center/right slots with the tokens
{{page}}, {{pages}} and {{date}}, or a full HTML
header with its own styles. Elements with the classes page,
pages and title are filled in on every page.
"footer": { "enabled": true,
"right": "Page {{page}} of {{pages}}" }
Custom fonts and web fonts
Load fonts the way you would on a website: a <link> to a font
stylesheet or an @font-face rule with a remote URL, fetched through our proxy.
Fonts from the engine's built-in catalog work without any setup.
Before capture, the engine waits for the page's load event, for fonts to
finish loading, for images to decode and for two rendered frames. That removes the
classic "PDF printed in the fallback font" bug.
JavaScript, charts and dynamic content
JavaScript runs by default, so Chart.js, D3, or a client-rendered app can draw before the
PDF is taken. Tell the engine what "ready" means: networkidle (500 ms
without requests), a CSS selector, a ready_flag your code sets,
or a fixed delay.
With strict: true a timeout fails the render; with false you get
the PDF plus a wait_timeout warning.
Page breaks and long tables
Control breaks with standard CSS: break-before: page starts a new page,
break-inside: avoid keeps a row or a card together. A table's
<thead> repeats at the top of every page it spans, so a 40-row invoice
stays readable on page three.
Pick one of 13 named sizes (A0–A6, B4, B5, Letter, Legal, Tabloid, Ledger) or set
width and height freely from 0.1 to 200 inches.
single_page makes one continuous page as tall as the content, which is how
58 mm and 80 mm thermal receipts are printed. page_ranges returns only
the pages you need, for example "1-3,5".
Password protection, bookmarks and metadata
protect sets a
user and an owner password plus seven individual permissions, such as printing or copying.
outline builds bookmarks from your h1–h6,
tagged adds the structure screen readers use, and metadata sets
title, author, subject, keywords and language.
Locale, time zone and colors
Dates and numbers that your page's JavaScript formats come out right when you set
locale and timezone; a US customer sees
9/22/2026, a German one 22.9.2026. Backgrounds and brand colors
print by default, no "background graphics" checkbox required.
HTML to PDF API vs. self-hosted Puppeteer vs. wkhtmltopdf
Most teams that search for an HTML to PDF service already have one of two alternatives in
mind: running headless Chrome themselves with Puppeteer or Playwright, or the
wkhtmltopdf binary. Here is how they compare. We only put numbers where we can
measure them; for self-hosting, the honest answer is "it depends on your setup".
HTML to PDF API compared with self-hosted Puppeteer and wkhtmltopdf
Criterion
Dynamic Document API
Self-hosted Puppeteer / Playwright
wkhtmltopdf
Setup
API key and one HTTPS request
Install Chromium and fonts, build a render service with a queue
Install the binary and system fonts
Engine
Chromium (Chrome 153), updated by us with each engine release
Chromium, the version you pin and patch
Legacy Qt WebKit; repository archived in 2023
CSS grid and flexbox
Yes
Yes
No grid; flexbox only in the old -webkit-box syntax
JavaScript
Yes, with built-in wait conditions
Yes, you write the wait logic
Old JS engine; waits via a fixed delay or window.status
€47/month (Growth, 15,000 renders, billed annually) or €59 monthly
Servers plus engineering time
Servers plus engineering time
Choose it when
You want correct PDFs without running browsers
Data must never leave your network, or you already operate a browser fleet
You maintain a legacy system and cannot change it yet
When a hosted API is not the right choice
If your documents must be generated in an air-gapped network, or policy forbids sending
their content to any third party, self-host Chromium. The same applies if you already run a
well-tuned browser fleet at very high volume: at that point the API mainly saves you
operations work, not money. For everyone else, the hosted route removes the part of PDF
generation that breaks at 2 a.m.: the browser.
Moving off wkhtmltopdf
Because wkhtmltopdf is based on an old WebKit, layouts written for it often contain
workarounds such as -webkit-box or float-based grids. They keep working in
Chromium, so you can switch the renderer first and modernize the CSS later. Its page-size and
margin flags map to pdf.paper and pdf.margin, and its header and
footer HTML options map to pdf.header and pdf.footer.
Raw HTML or reusable templates?
Sending raw HTML is the right choice when your application already produces the markup,
for example with React server rendering, Django, Rails or Laravel views. You keep the
layout in your repository and the API only prints it.
When the same layout is filled with different data thousands of times, store it once and
send only JSON. Reusable PDF templates use Jinja syntax,
format dates and currencies per locale through ICU, render QR codes and barcodes, and keep every published version, so you can pin a version and roll back a layout change. A
typical case is to turn an HTML invoice into a PDF for each
order.
Writing long-form content such as reports, manuals or LLM output? You can also
start from Markdown instead of HTML and pick one of four
print themes.
Large documents: sync, async and webhooks
By default a request is synchronous: the connection stays open until the PDF is ready, and
most renders finish in well under a second. If a render takes longer than your plan's sync
limit, the API does not fail; it answers 202 Accepted with the render ID and
keeps working. You fetch the result later from GET /v1/renders/{id}.
For large reports or batch jobs, send "mode": "async" with a webhook. The API
answers immediately, and your endpoint receives render.succeeded or
render.failed when the job is done. Send an Idempotency-Key header
and you can retry a request safely: the same key returns the same render for 24 hours
instead of creating a second one.
Synchronous requests return the PDF directly. Asynchronous requests return at
once and notify your webhook when the file is ready.
Security and data handling
Regional processing. Render payloads and generated files stay in
the region of the API host you call.
Signed, expiring links. Download URLs are signed and expire after
one hour by default. Set expires_in anywhere from 60 seconds to 7 days,
or skip hosting and take the file as binary.
Retention you control. Choose how long files are kept per request
with retention_days, up to your plan's maximum, and purge them at any time
with DELETE /v1/renders/{id}/files.
Your own storage. On Enterprise plans, files can go straight to
your own S3-compatible bucket.
Safe asset loading. Images, fonts and stylesheets referenced by
your HTML are fetched through a proxy that blocks private and internal network
addresses.
Test and live keys. Separate API keys for development and
production, and a Data Processing Agreement on every paid plan.
HTML to PDF API pricing
Start with 50 free renders a month, no credit card. Paid plans are flat monthly
prices with a fixed volume and automatic top-ups, so the cost per document is easy to
calculate and a pipeline never stops at month end.
Free
€0
50 renders per month, full REST API. For testing and
prototypes.
Starter
€15 / month
3,000 renders per month, billed annually (€19 billed monthly). About
€0.005 per render.
Higher volumes, team features and your own storage are on the larger plans. See the full
HTML to PDF API pricing for every tier.
FAQ
HTML to PDF API: frequently asked questions
Does the API support CSS grid and flexbox?
Yes. The engine is Chromium (Chrome 153), so flexbox, grid, custom properties,
@page rules and print media queries behave as they do in current Chrome.
A quick way to preview a layout is Chrome's own print preview: it uses the same
layout engine.
How do I add page numbers and a running footer?
Set pdf.footer to {"enabled": true, "center": "Page {{page}} of {{pages}}"},
or pass your own footer HTML with elements that carry the classes page and
pages. Reserve room for it with margin.bottom. Headers work the
same way through pdf.header.
Does it run JavaScript before generating the PDF?
Yes, JavaScript is on by default. Use pdf.wait.until to decide when the page
is ready: networkidle, a CSS selector, a ready_flag
your script sets, or a fixed delay. Set pdf.javascript to
false to turn scripts off.
How long can a render take?
Most renders are fast: the median is 216 ms and p99 is 667 ms, measured as queue plus
processing time without network. When a page waits for content, the wait timeout
defaults to 30 seconds and can be raised to 300 seconds. If a synchronous request
outlasts your plan's sync limit, the API answers 202 and finishes the render
asynchronously.
Can I convert a URL instead of raw HTML?
Yes. POST /v1/pdf/from-url lets you
convert a live URL to PDF, including pages behind a login
via cookies, headers or basic auth. Credentials are only sent to the same domain. Custom
CSS cannot be injected into a URL capture; if you need to restyle the page, send its HTML
instead.
How is this different from wkhtmltopdf?
wkhtmltopdf renders with a legacy Qt WebKit engine, and its repository was archived in
2023, so it no longer receives fixes. It does not support CSS grid and handles modern
JavaScript poorly. Dynamic Document API renders with current Chromium and you do not install or
patch anything.
Is there a free HTML to PDF API tier?
Yes. The free plan includes 50 renders per month with the full REST
API and no credit card. Paid plans start at €15 per month for 3,000 renders when billed
annually, or €19 billed monthly.
Convert your first HTML to PDF free
50 renders a month on the free plan, no credit card, auto top-ups on paid plans, no browser to maintain.