Markdown to PDF API
Markdown to PDF API: convert with marked, markdown-it, Python-Markdown or goldmark, add a print stylesheet and code highlighting, and get a PDF.
Markdown is the easiest way to write a document, and it's what most AI agents produce by default. Getting from Markdown to a good-looking PDF takes two steps. First turn the Markdown into HTML, then print that HTML with a browser engine.
This guide covers the conversion in JavaScript, Python and Go, a minimal print stylesheet, code highlighting, and the API call that produces the PDF.
Quick answer. Convert your Markdown to HTML with any parser, then POST the HTML to https://html-pdf.com/v1/pdf and save the response as a PDF.
curl https://html-pdf.com/v1/pdf -H "Authorization: Bearer $HTML_PDF_KEY" \
-d '{"html":"<h1>Report</h1><p>Rendered from Markdown.</p>"}' -o report.pdf
Step 1: Markdown to HTML
This step turns your Markdown into HTML with the parser for your language. All of these support GitHub-style tables and fenced code blocks.
JavaScript with marked:
import { marked } from "marked"; // npm install marked
const body = marked.parse("# Report\n\n| Item | Qty |\n|---|---|\n| Widget | 3 |");
JavaScript with markdown-it:
import markdownit from "markdown-it"; // npm install markdown-it
const body = markdownit({ html: false, linkify: true }).render(source);
Python with Python-Markdown:
import markdown # pip install markdown
body = markdown.markdown(source, extensions=["tables", "fenced_code", "toc"])
Go with goldmark:
import (
"bytes"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/extension"
)
var buf bytes.Buffer
md := goldmark.New(goldmark.WithExtensions(extension.GFM))
if err := md.Convert(source, &buf); err != nil { panic(err) }
body := buf.String()
If the Markdown comes from users or an LLM, keep raw HTML disabled (the markdown-it default) or sanitize the output. DOMPurify and bleach both work well for this.
Step 2: Add a print stylesheet
This section gives you a stylesheet that makes the output look like a real document. Raw parser output prints in Times New Roman with no margins, so wrap it in a full HTML document with a small stylesheet. This one is a good default for reports, docs and READMEs.
@page { size: A4; margin: 20mm 18mm; }
html { font: 11pt/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: #1f2328; }
body { margin: 0; }
h1, h2, h3 { line-height: 1.25; margin: 1.4em 0 0.5em; break-after: avoid; }
h1 { font-size: 22pt; margin-top: 0; }
h2 { font-size: 15pt; border-bottom: 1px solid #d0d7de; padding-bottom: 0.2em; }
h3 { font-size: 12.5pt; }
p, ul, ol, table, pre, blockquote { margin: 0 0 0.9em; }
a { color: #0969da; text-decoration: none; }
code { font: 9.5pt ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
background: #f3f4f6; padding: 0.1em 0.3em; border-radius: 3px; }
pre { background: #f6f8fa; padding: 10px 12px; border-radius: 6px;
white-space: pre-wrap; word-break: break-word; break-inside: avoid; }
pre code { background: none; padding: 0; }
table { border-collapse: collapse; width: 100%; font-size: 10pt; }
th, td { border: 1px solid #d0d7de; padding: 5px 8px; text-align: left; }
th { background: #f6f8fa; }
tr, img { break-inside: avoid; }
img { max-width: 100%; }
blockquote { border-left: 3px solid #d0d7de; padding-left: 12px; color: #57606a; }
hr { border: 0; border-top: 1px solid #d0d7de; margin: 1.5em 0; }
white-space: pre-wrap stops long code lines from running off the page. The break-* rules keep headings with their content and table rows in one piece. More on that in HTML to PDF page breaks.
Code highlighting
This section shows how to get colored code blocks in the PDF. Highlight at conversion time so the HTML already contains colored spans, and inline the theme CSS. Nothing has to load or run in the browser.
In Node.js, use highlight.js with marked-highlight.
// npm install marked marked-highlight highlight.js
import fs from "node:fs";
import { Marked } from "marked";
import { markedHighlight } from "marked-highlight";
import hljs from "highlight.js";
const marked = new Marked(markedHighlight({
emptyLangClass: "hljs",
langPrefix: "hljs language-",
highlight(code, lang) {
const language = hljs.getLanguage(lang) ? lang : "plaintext";
return hljs.highlight(code, { language }).value;
},
}));
const hljsCss = fs.readFileSync("node_modules/highlight.js/styles/github.css", "utf8");
In Python, the codehilite extension does the same with Pygments.
Step 3: HTML to PDF
This step prints your HTML to a PDF. The DIY route is a headless browser, either Puppeteer or Playwright with page.setContent(html) then page.pdf(), or chrome --headless --print-to-pdf.
It works, but you have to run and maintain Chromium yourself. See the Puppeteer guide.
Or skip the server
This section shows how to get the PDF without running a browser. Send the HTML to html-pdf.com and get the PDF back. Here is a complete Node.js script that continues from the highlighting snippet above.
const docCss = fs.readFileSync("print.css", "utf8"); // the stylesheet above
const source = fs.readFileSync("report.md", "utf8");
const html = `<!doctype html><html><head><meta charset="utf-8"><title>Report</title></head>
<body>${marked.parse(source)}</body></html>`;
const res = await fetch("https://html-pdf.com/v1/pdf", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.HTML_PDF_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({
html,
css: docCss + hljsCss,
prefer_css_page_size: true,
footer_html: '<div style="font-size:8px;width:100%;text-align:center;color:#888"><span class="pageNumber"></span> / <span class="totalPages"></span></div>',
max_pages: 50,
}),
});
if (!res.ok) throw new Error(await res.text());
fs.writeFileSync("report.pdf", Buffer.from(await res.arrayBuffer()));
The same thing in Python:
import os, markdown, requests
body = markdown.markdown(open("report.md").read(), extensions=["tables", "fenced_code"])
html = f'<!doctype html><html><head><meta charset="utf-8"></head><body>{body}</body></html>'
r = requests.post("https://html-pdf.com/v1/pdf",
headers={"Authorization": f"Bearer {os.environ['HTML_PDF_KEY']}"},
json={"html": html, "css": open("print.css").read(), "prefer_css_page_size": True})
r.raise_for_status()
open("report.pdf", "wb").write(r.content)
prefer_css_page_size makes the @page size and margins in the stylesheet win. If you add a footer, the bottom page margin needs room for it.
Use "response": "url" to get a download link valid for 24 hours instead of the file.
AI agents writing reports in Markdown
This section explains how an AI agent can go from Markdown to PDF on its own. Agents already think in Markdown. Connect the PDF MCP server to Claude, ChatGPT, Cursor or another MCP client, and you can ask "Write the quarterly summary and give me a PDF".
The agent converts its Markdown to HTML, adds a stylesheet, and calls html_to_pdf. In your own agent code, the two snippets above are the whole pipeline.
Pricing
This is what a Markdown PDF costs. 1 credit = 1 PDF page. You get 100 free credits once, then packs from $10 for 1,000 pages down to $0.005 per page at $250 for 50,000.
Purchased credits never expire, and failed renders are free.
Get a free API key · API docs · Guides · Templates
FAQ
How do I convert Markdown to PDF with an API?
Convert the Markdown to HTML with a parser such as marked, markdown-it, Python-Markdown or goldmark. Then POST the HTML to https://html-pdf.com/v1/pdf with your API key and save the response as a PDF.
How do I add code highlighting to a Markdown PDF?
Highlight the code when you convert Markdown to HTML, for example with highlight.js and marked-highlight in Node.js or codehilite in Python. Send the theme CSS along with the HTML so the colors are already in the page.
How much does Markdown to PDF conversion cost?
1 credit = 1 PDF page, and you get 100 free credits once with no card. After that, packs range from $10 for 1,000 pages to $250 for 50,000 pages, and purchased credits never expire.
Can an AI agent turn Markdown into a PDF?
Yes. Connect the PDF MCP server to your agent and it can convert its Markdown to HTML and call the html_to_pdf tool to return a PDF.
