There's a moment every developer knows. You need to generate a PDF. It looks simple. You've done harder things.
Three hours later, you're reading a Stack Overflow thread from 2016 that ends with "works on my machine."
This post is about that moment — the actual options, what breaks in each, and where I landed after years of hitting this in production.
The options are far from perfect
1, wkhtmltopdf: The Aging Classic
It uses a stripped-down WebKit engine and converts HTML to PDF directly. Simple to install, simple to use, and it works fine if your document is simple HTML from 2012.
The problems show up when you use modern CSS:
- No Flexbox support (or extremely broken)
- No CSS Grid
- No support for
@page: Doing headers/footers is a nightmare. - The last release was 2019. The project is unmaintained.
For basic invoices with tables and some styling, wkhtmltopdf can still work. The moment your designer touches the template, you have a problem.
2, Puppeteer / Playwright
The modern approach. Spin up a real headless Chrome, navigate to the URL or inject HTML, call page.pdf(). Full CSS and JS support — if Chrome renders it, this renders it.
The infrastructure cost is real:
Docker: Chrome needs specific flags to run inside a container (--no-sandbox, --disable-setuid-sandbox). Get one wrong and you get a cryptic crash at 2am. You also need specific base images — the official Chrome Docker setup is not a one-liner.
Memory: Chrome is not lightweight. A pool of 3 Chrome instances will use 600–900MB on a modest server. Under load spikes, you'll OOM.
Display server: Puppeteer needs a display to run. In a standard Linux server environment, you need Xvfb or the --headless flag handled correctly. Miss this and the server works locally and fails in CI.
Version pinning: Chrome updates frequently. The Puppeteer version and Chrome version need to stay in sync or you'll get subtle rendering changes in production that nobody notices for weeks.
None of these are insurmountable. They're all solved problems. But they're also not your problem to solve — they're infrastructure problems that eat hours if not days that should go to your product.
3, LibreOffice headless
For DOCX, XLSX, PPTX → PDF, LibreOffice is essentially the only real option. Nothing else handles Office formats reliably without a full Office licence.
The problems:
- Needs a display server (same as Puppeteer)
- Memory leaks under sustained load — needs periodic restarts
- Rendering fidelity varies by document complexity
- Startup time is slow (200–400ms cold, longer under load)
LibreOffice is powerful software doing a hard job. It's just not something you should be babysitting in your application layer.
What I wanted (and why I built APIJolt)
I'd hit all of the above in different projects. What I wanted was simple:
- One HTTPS call, get a PDF back
- Every format: HTML, URL, DOCX, XLSX, PPTX, images
- Flat pricing — no credits, no expiring balances
- Files not stored indefinitely
So I built APIJolt.
How the API works
The simplest case — HTML string to PDF:
curl -X POST https://api.apijolt.com/v1/html-to-pdf \
-H "Authorization: Bearer aj_live_xxxx" \
-H "Content-Type: application/json" \
-d '{"html": "<h1>Invoice #1042</h1><p>Due: $240.00</p>"}'
You get a binary PDF back. That's it.
For a URL conversion (useful when your app already renders the page):
curl -X POST https://api.apijolt.com/v1/url-to-pdf \
-H "Authorization: Bearer aj_live_xxxx" \
-H "Content-Type: application/json" \
-d '{"url": "https://yourapp.com/invoice/1234"}'
For no-code tools (n8n, Zapier, Make) where you need JSON not binary:
{
"html": "<h1>Invoice</h1>",
"response_type": "base64"
}
The response_type parameter covers all integration patterns without adapters: binary for direct streaming/storage, base64 for JSON workflows, url for async jobs.
For DOCX → PDF:
curl -X POST https://api.apijolt.com/v1/word-to-pdf \
-F "file=@contract.docx" \
-H "Authorization: Bearer aj_live_xxxx"
Full format support: HTML, URL, Markdown, DOCX/DOC/ODT/RTF, XLSX/XLS/ODS/CSV, PPTX/PPT/ODP, JPG/PNG/WebP/GIF/BMP/TIFF. There's also a /v1/convert-to-pdf endpoint that auto-detects the format.
What's under the hood
Chrome pool (Puppeteer) for HTML and URL conversions — full CSS3, Flexbox, Grid, JavaScript. LibreOffice headless for Office formats. ImageMagick for images. It's the same stack you'd build yourself — I'm just managing it so you don't have to.
Files are deleted after 1 hour. Not in a Trust Center — right here.
The pricing decision
I debated this a lot. Credit systems are the default in this space and they make revenue predictable from the provider side. But from the user side, credits expire, overages surprise you and you're constantly aware of a meter running.
I went with flat monthly with a hard cap and an upgrade prompt. You know exactly what you're paying. No surprises.
The free tier is 1,000 conversions/month, all formats.
Help me shape the Roadmap
I'm currently in beta and I want to build what you actually need.
Should I focus on:
- Better templating (sending JSON data to a pre-defined layout)?
- Tighter GDPR/Data Privacy compliance?
- Direct S3/Google Drive uploads?
If you have 90 seconds, I’d love your expert feedback in this quick survey. Your input will literally determine what I build in the next 3 months.
Try it out: apijolt.com (No card required, first conversion in under 5 minutes).
I'll be in the comments — let me know how you're currently handling (or suffering through) PDF generation!
Top comments (1)
Thanks for reading! I'm curious: what's the weirdest CSS/layout bug you've encountered when trying to generate a PDF? For me, it was wkhtmltopdf failing on transparent PNGs for 4 hours straight.