Markdown to PDF Is a Swamp
“Convert this Markdown to a PDF” sounds like a checkbox on a feature list. A one-liner. Every language has a library; every doc tool claims it. I believed that too, until I’d walked into the same swamp from four different directions and finally stopped to look at the ground.
The first thing to understand is that there is no such thing as Markdown-to-PDF. It’s never one hop. It’s Markdown → HTML → PDF, or Markdown → LaTeX → PDF, and every link in that chain has its own idea of what your document should look like. There’s no canonical pipeline — just a pile of tools that each break differently, which is exactly why “my experience with Markdown-to-PDF has been inconsistent” is the universal review.
A tour of engines, none of which is the answer
- wkhtmltopdf is the classic HTML-to-PDF workhorse, and it is a trap. It’s frozen on an ancient WebKit, so modern CSS — flexbox, grid — silently doesn’t render the way you wrote it. And here’s the part that should stop you cold: it was archived in January 2023 with an unpatched SSRF vulnerability rated CVSS 9.8 that will never be fixed — and it still generates an enormous share of the world’s HTML-to-PDF. The default tool for the job is a security-abandoned relic that quietly drops your layout.
- pandoc with XeLaTeX gives you genuinely beautiful typesetting and real Unicode (which matters the moment you mix scripts) — at the cost of dragging in a massive LaTeX toolchain and needing to know LaTeX to restyle anything.
- Headless Chrome is the gold standard: it renders modern CSS exactly right, because it’s a real browser. It’s also the one engine you frequently can’t run — restricted corporate boxes, locked-down serverless, environments where you don’t get to install Chromium.
- Client-side libraries —
jsPDF,pdfmake, the browser’s ownwindow.print()— are fundamentally limited for anything that has to look like a real document.window.print()pollutes the output with browser chrome. I’d left this exact problem unresolved in an earlier tool because there was no clean client-side answer.
Sit with the shape of that: the one engine that renders correctly is the one you often can’t use, and the one everyone actually uses is a dead, insecure relic. That’s the swamp.
The silent ways it goes wrong
The failures aren’t loud. Unicode and emoji come out garbled. Markdown leaks through the HTML export unrendered, as literal asterisks. Page breaks and fonts drift between what you previewed and what exported. And the one that genuinely broke me: a single report, exported four ways — web view, PDF, HTML, JSON — produced four different versions of its own executive summary. One source document, four “export” buttons, four different truths. That’s the whole “inconsistent and spread across things” complaint compressed into a single artifact.
The document I torture every converter with
This isn’t abstract, and it’s reproducible in about a minute. My standard test file is a single page that pokes every soft spot at once: a line of emoji and mixed scripts, a fenced code block, a wide table, a Mermaid diagram, a KaTeX equation, and a hard page break. Then I export it every way the tool offers — PDF, HTML, Word, an image — and lay the results next to each other.
Almost nothing survives all of them intact, and the breakages line up exactly with the engines above. The wkhtmltopdf-style path drops the emoji and shoves the wide table past the margin, because its frozen WebKit never learned the modern CSS. The pure client-side libraries print the equation as literal $…$ and the diagram as its raw Mermaid source, because they can’t render either. The HTML always looks perfect — because it’s the one format that never had to commit to a fixed page. One file, every export button, and no two outputs agreed — and the only way you’d have known is by making them all and comparing by hand. That’s the swamp in a single file.
The real crime is that a PDF export is a blackbox
Here’s the workflow every one of these tools hands you: write your template, hit export, download the file, open it, discover it’s wrong, and go back. You only learn the PDF broke after you’ve made it. There’s no honest live preview, because the fast preview you’re looking at — an iframe rendering the HTML — is not what the PDF engine will produce. You are debugging blind, one full export at a time.
So I built mdbuild, and it can’t fully win either
mdbuild is a browser-based Markdown editor that exports the same document to PDF, HTML, Word, and PNG — GitHub-flavored Markdown, Mermaid diagrams, and KaTeX math included — entirely client-side. No account, no external API calls, and, in its own words, all processing happens in your browser; it never sees your documents. The design concession that admits the whole problem is that it has two previews, not one. An instant preview that’s fast but honest about being approximate — fonts and page breaks may differ. And a fidelity preview, a debounced call to the actual render engine shown through PDF.js, that is exactly what the export will be. Two previews, because a single “preview” that didn’t match the export would be a lie, and lying about the PDF is the original sin of every tool in the swamp.
And I’ll be straight about the tension I couldn’t design my way out of: the only way to guarantee a pixel-perfect PDF is a server-side headless-Chromium step — which breaks the entire client-side, nothing-leaves-your-browser promise the tool is built on. Perfect fidelity and real privacy pull in opposite directions. I chose the privacy side and paid for it with the two-preview compromise, and I think that’s the right trade — but it is a trade, not a solution.
“Markdown to PDF” reads like a checkbox. Underneath it is a swamp with a dead engine at its center, a blackbox for a workflow, and a fidelity-versus-privacy fork with no clean exit. mdbuild doesn’t drain the swamp. It hands you a boat and an honest map — two previews, and the truth about which one to trust.
Sources & further reading
- wkhtmltopdf on GitHub — archived January 2023, unmaintained.
- CVE-2022-35583 (NVD) — the wkhtmltopdf SSRF, rated CVSS 9.8.
- pandoc — creating a PDF — PDF output through a LaTeX engine like XeLaTeX.