HTML to PDF API
PDFend converts your HTML and CSS into pixel-perfect PDF documents with a single API call. No headless Chrome to manage, no Puppeteer infrastructure, no zombie processes. Just send HTML, receive a PDF URL — typically under two seconds.
Under the hood, PDFend runs the same Chromium rendering engine you use in your browser. If it looks right in print preview, it will look right in the generated PDF. That means full CSS3 support: Flexbox, Grid, CSS custom properties, gradients, SVG, web fonts, box shadows — everything. No surprises, no “almost works” edge cases.
A 30-second example
One POST request, one PDF URL. This is the smallest valid example — copy it into your terminal after replacing the API key.
curl -X POST https://api.pdfend.com/v1/generate \
-H "Authorization: Bearer pk_live_..." \
-H "Content-Type: application/json" \
-d '{
"html": "<h1>Invoice #1234</h1><p>Total: $99.00</p>",
"options": { "format": "A4", "print_background": true }
}'{
"id": "pdf_01HXABC",
"status": "completed",
"url": "https://files.pdfend.com/pdf_01HXABC.pdf",
"pages": 1,
"size": 24830,
"duration_ms": 812
}What the API supports
PDFend is a thin, opinionated wrapper around the Chromium printing pipeline. Every option exists because real customers asked for it, not because it was easy to add.
Full CSS3 rendering
Flexbox, Grid, custom properties, gradients, transforms, media queries — everything Chromium supports, because that is what we use under the hood.
Tailwind CSS ready
Ship Tailwind utility classes directly in your HTML. No build step, no PurgeCSS configuration — paste your component, get a PDF.
Google Fonts auto-loading
Our renderer detects font-family declarations and injects the matching Google Fonts stylesheet automatically. Use any of 1400+ families without extra <link> tags.
Headers and footers
Separate header and footer HTML with page number tokens ({{page}} and {{pages}}), different content on the first page, and full styling.
Page break control
Use page-break-before, page-break-after, and break-inside: avoid. Keep tables, invoice line items, and signature blocks together.
Six paper formats
A3, A4, A5, Letter, Legal, Tabloid out of the box — plus custom margins, landscape mode, and a scale factor for precise fitting.
Using it from TypeScript
The @pdfend/sdk package ships with full types and zero runtime dependencies. It works in Node 18+, Bun, Deno, and any edge runtime that supports fetch.
import { PDFend } from "@pdfend/sdk";
const pdfend = new PDFend(process.env.PDFEND_API_KEY!);
const { url, pages } = await pdfend.generate({
html: renderInvoice(order),
options: {
format: "A4",
margin: { top: "20mm", bottom: "20mm", left: "15mm", right: "15mm" },
headerHtml: `<div style="font-size:10px;width:100%;text-align:right">
${order.company}
</div>`,
footerHtml: `<div style="font-size:10px;width:100%;text-align:center">
Page {{page}} of {{pages}}
</div>`,
printBackground: true,
},
});
console.log("PDF ready:", url, "(" + pages + " pages)");Using it from Python
The pdfend package on PyPI has the same shape — a single client object, a generate method, typed results. It works in Django, FastAPI, Flask, and plain scripts.
from pdfend import PDFend
client = PDFend("pk_live_...")
result = client.generate(
html=render_invoice(order),
options={
"format": "A4",
"margin": {"top": "20mm", "bottom": "20mm"},
"header_html": f"<div style='text-align:right'>{order.company}</div>",
"footer_html": "<div>Page {{page}} of {{pages}}</div>",
"print_background": True,
},
)
print(f"PDF ready: {result['url']} ({result['pages']} pages)")How it works
Every request hits our edge API, authenticates your key, checks your rate limit, and either renders the PDF synchronously or queues it for async processing. The render step runs in isolated Chromium workers with per-request browser contexts — memory is reset between jobs, so nothing leaks.
- Auth + validation. Your API key is checked against a plan and rate limit. The request body passes through a Zod schema, so invalid payloads return 400 with field-level details.
- Template resolution. If you sent a template slug, we compile Handlebars against your data. Otherwise we use the raw HTML.
- Font injection. We parse
font-familydeclarations and automatically inject the matching Google Fonts stylesheet if you didn't include one yourself. - Render. A Chromium worker loads the HTML, waits for
networkidle, applies the paper format and margins, then emits the PDF. - Upload + response. The PDF is stored in S3-compatible storage (Cloudflare R2 in production) and you receive a signed URL.
What people build with it
PDFend is not opinionated about what you print — if it is HTML, we turn it into a PDF. Here are the four workloads that drive 80% of our traffic.
Invoices and receipts
Render one invoice per request or batch them overnight. Dynamic totals, line items, taxes, multi-currency — HTML handles it, we print it.
Reports and analytics exports
Weekly PDFs for stakeholders, quarterly KPI dashboards, SOC-style audit summaries. Include charts by rendering SVG or canvas-to-image beforehand.
Contracts and agreements
MSA, NDA, DPA — long documents with headers, footers, page numbers, and carefully controlled page breaks so clauses never split awkwardly.
Certificates and tickets
Course certificates, event tickets, gift cards — fixed-layout single-page PDFs with custom fonts, barcodes (render as SVG), and background images.
Async generation and webhooks
Small receipts render in under a second; quarterly reports with 200 pages and chart images can take 15–20 seconds. For anything longer than a few seconds, flip async: trueand we'll queue the job and POST to your webhook when done.
{
"html": "<html>...</html>",
"async": true,
"webhook_url": "https://yourapp.com/webhooks/pdfend"
}The webhook payload mirrors the sync response. Retries use exponential backoff, and you can verify authenticity via theX-PDFend-Signature header (HMAC-SHA256 of the raw body with your webhook secret).
Performance and reliability
The median sync request completes in 800–1200 ms for a single-page invoice. Chromium is warm-started and pooled, so there's no cold boot penalty. We run workers across multiple availability zones and aim for 99.95% monthly uptime on Pro and Scale plans, with a public status page.
Because every render runs in its own browser context, one customer's runaway JavaScript can't affect anyone else. We also cap request timeouts at 30 seconds (sync) and 180 seconds (async) to keep the fleet healthy.
Security and compliance
Your HTML is rendered in a sandboxed Chromium instance. Network requests are filtered to block private IP ranges (no SSRF into internal infrastructure), and each render has a strict CPU and memory budget. PDFs are stored in encrypted S3-compatible buckets and served over TLS-only URLs.
API keys are hashed at rest with Argon2. Rotating a key is a one-click action in the dashboard — the old key keeps working for five minutes so in-flight requests complete.
Frequently asked questions.
01How accurate is the HTML to PDF rendering?+
02Can I use Tailwind CSS classes directly in the HTML?+
03Do you support custom fonts?+
04How do I handle page breaks?+
05What is the maximum PDF size I can generate?+
06Is the API rate-limited?+
07Do you store the generated PDFs?+
08Can I self-host PDFend?+
Related pieces.
Render React components to HTML and hand them to PDFend — perfect for Next.js server actions.
wkhtmltopdf is deprecated. Swap the CLI call for a single HTTP request.
Every option the /v1/generate endpoint accepts, with examples.
Store HTML once, render per request with data. Loops, conditionals, helpers included.
Ready to convert HTML to PDF?
Create a free account, grab an API key, make your first request in under a minute. 100 PDFs a month on the free tier, forever.