How to Batch Convert Multiple Markdown Files to PDF
Three common batch jobs:
- One Markdown file → one PDF, repeated for many files:
md-to-pdf *.md(npm CLI). - Many Markdown files → one combined PDF (a book, a report with chapters):
pandoc chapter*.md -o book.pdf. - Many Markdown files → one PDF on each commit: GitHub Actions workflow with the npm CLI.
The browser-based /markdown-to-pdf is single-file; for batch jobs you want a CLI. This guide walks through the three scenarios with copy-paste commands and a few gotchas.
The simplest case. You have chapter01.md, chapter02.md, ..., and you want chapter01.pdf, chapter02.pdf, ...
npm install -g md-to-pdf
md-to-pdf 'chapters/*.md'
That's it — by default it writes a .pdf next to each .md. Add a stylesheet to control look:
md-to-pdf 'chapters/*.md' --stylesheet ./style.css
If you have hundreds of files and want to parallelize:
find chapters -name '*.md' -print0 | xargs -0 -P 4 -I {} md-to-pdf '{}'
Start with low concurrency and increase it while observing CPU and memory; each worker may start a browser process.
This is what you want for books, technical reports, or onboarding docs split across files.
pandoc chapter01.md chapter02.md chapter03.md -o book.pdf
Gotcha: filename order matters. Pandoc concatenates in argument order, not alphabetical, so use shell expansion intentionally:
pandoc chapter*.md -o book.pdf # alphabetical order, fine if you've zero-padded
For a table of contents:
pandoc chapter*.md --toc -o book.pdf
With proper LaTeX page breaks between chapters:
pandoc chapter*.md --toc --top-level-division=chapter -o book.pdf
If you don't want to install Pandoc, concatenate first:
: > combined.md
for f in chapter*.md; do
cat "$f" >> combined.md
printf '\n\n<div style="page-break-before: always;"></div>\n\n' >> combined.md
done
md-to-pdf combined.md
The <div> injects a page break between chapters. Crude but reliable.
A GitHub Actions workflow that produces PDFs on every push to main:
# .github/workflows/build-pdfs.yml
name: Build PDFs
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm install -g md-to-pdf
- run: md-to-pdf 'docs/**/*.md'
- uses: actions/upload-artifact@v4
with:
name: pdfs
path: 'docs/**/*.pdf'
This runs md-to-pdf on every Markdown file under docs/, uploads the PDFs as a build artifact. Trigger on tag push and you can attach them to a Release.
- Image paths break in the combined PDF. If
chapter01.mdreferences./images/a.pngfrom inside its own directory, concatenating into a single file breaks that path. Either rewrite paths to be absolute, embed images as data URIs, or use Pandoc's--resource-path=.to add lookup directories. - Front-matter conflicts. Each
.mdfile may have its own YAML front-matter. Pandoc takes the first;md-to-pdfper-file mode reads each file's own. Strip front-matter before concatenating if it conflicts. - Page numbers reset between chapters. Pandoc handles this with
--top-level-division=chapter; the cat-and-convert path doesn't, so page numbers are continuous (which is usually what you want for a book). - Unicode and CJK fonts. With Pandoc and XeLaTeX, set an installed font explicitly, for example
--pdf-engine=xelatex -V mainfont:"Source Han Serif CN". Local Chromium tools use fonts available in their runtime. For hosted rendering, test the service font coverage with the exact characters you need. - Memory for large batches. Browser-based conversion is resource-intensive. Bound concurrency, monitor memory, and split very large jobs into resumable chunks.
- One file occasionally →
/markdown-to-pdf. Faster to open the page than to install npm. - Same template, dozens of files, one-off →
md-to-pdfCLI. - Same template, many files, on every commit → CLI in CI.
- Books, reports, multi-chapter → Pandoc with
--top-level-division=chapter --toc. - Heterogeneous templates per chapter → generate per-file PDFs (Scenario 1) and merge them with
pdfuniteorqpdfafterward.
For more on each tool's tradeoffs see our Markdown to PDF method comparison and the deep dive on code highlighting in PDF.
If you already produced separate PDFs and want to concatenate without re-rendering:
# pdfunite (poppler-utils)
pdfunite chapter01.pdf chapter02.pdf chapter03.pdf book.pdf
# or qpdf
qpdf --empty --pages chapter*.pdf -- book.pdf
# or ghostscript
gs -sDEVICE=pdfwrite -dNOPAUSE -dBATCH -sOutputFile=book.pdf chapter*.pdf
Merging avoids re-rendering, so content quality is preserved; elapsed time depends on file count, size, storage, and the selected tool.
Can I batch-convert with the web /markdown-to-pdf?
The web tool is single-file. For batch jobs use the CLI; for one-off use the web tool. Some users zip up a batch result manually after running CLI conversions.
How do I preserve code highlighting across all files in batch mode?
The CLI inherits whatever stylesheet you pass with --stylesheet. Use the same theme on every file for consistency. Read more in Markdown to PDF with code highlighting.
What's the upper limit on a single combined PDF?
There is no universal page limit: memory, images, fonts, renderer, and PDF viewer all matter. Test representative large documents, set CI timeouts, and split very large manuals into volumes.
Does Pandoc preserve Mermaid diagrams in batch mode?
Not natively. You need pandoc-mermaid-filter or convert Mermaid to SVG first, then reference the SVG. The npm CLI also needs a markdown-it Mermaid plugin for batch.
The right batch tool depends on what you're producing:
- 1-to-1 PDFs →
md-to-pdf '*.md' - Many-to-1 book →
pandoc chapter*.md --toc -o book.pdf - Continuous build → GitHub Actions + npm CLI
- Concatenate existing PDFs →
pdfuniteorqpdf
If this is a one-off batch and you don't have any of these installed, the lowest-friction path is: run the web /markdown-to-pdf once per file in a tab, then pdfunite the results.
The product limits and commands above were checked against these current primary sources:
Open MarkdownToImage to render Markdown, then choose the output format that matches your workflow. Test one representative document before automating a batch.