Turn Markdown into polished, branded PDFs. You write Markdown with a small YAML header; the pipeline merges in a brand (colours, fonts, logo, cover), renders it through Pandoc and XeLaTeX with a Lua filter and an Eisvogel-based LaTeX template, and produces a professional document - title page, table of contents, styled tables, charts, callout boxes, endnote references and all.
No LaTeX knowledge required to author. One command to build:
md-to-pdf report.mdThe pipeline grew out of a single shell script, compile.sh (2018), written to
assemble and version a multi-part report - "An Open Digital Approach for the
NHS" - from Markdown fragments for OpenUK. That script proved the idea: authors
write Markdown, the machine handles layout, versioning and branding.
It then generalised. md-to-pdf.sh added a brand system so the same content
could be produced under different visual identities, a Lua filter for richer
constructs (styled datatables, charts, callout boxes), and a professional LaTeX
template. In mid-2026 it was hardened into a real tool: robust YAML parsing, a
three-layer template architecture over a vendored upstream Eisvogel, brands as
self-contained external folders, an installer and a Debian package, and Claude
"skills" so an assistant can draft documents that build first time.
The goal is a pipeline that has matured past a personal wrapper - usable and shareable by others, with brands managed independently.
- Reports, briefings, and policy documents that need to look professional.
- The same content rendered under several organisations' brands.
- Print-ready output (crop marks, binding-aware layout) as well as digital.
- AI-assisted drafting: an assistant with the skill writes house-format, ready-to-build Markdown.
Three layers keep content, features, and identity independent:
- Base template (look): page geometry, title page, headers/footers, TOC.
eisvogel-wrapper.latex(pristine Eisvogel 3.4.0 + two small inserts) is the default;mvp.latexis a minimal standalone alternative;letter.latexis a window-envelope letter format (address block, date/refs, optional letterhead);beamer.latexproduces classic beamer slides, andslides.latexa modern full-bleed slide deck - both wired to the brand palette. Selectable per brand or per document withtemplate:. - Pipeline preamble (
pipeline-preamble.tex): the portable shim that loads every LaTeX package the filter's output needs (tables, boxes, charts). Any template that pulls it in supports the full feature set - seepandoc/documentation/TEMPLATE-CONTRACT.md. - Brand (identity): colours, fonts, heading colours, title-page logo/cover.
Each brand is a folder
<name>/template.yamlplus its assets.
A build flows: gather Markdown → parse front matter → merge the brand → run the Lua filter (boxes, datatables, charts → LaTeX) → render to PDF with XeLaTeX.
From the Debian package (pulls Pandoc and the required TeX Live sets):
sudo apt install ./pandoc-wrapper_1.0.0_all.debOr from a checkout, per-user or system-wide:
./scripts/install.sh # ~/.local
./scripts/install.sh --system # /usr/localThe VERSION file is the single source of truth; scripts/bump-version.sh
stamps it into the driver (SCRIPT_VERSION) and the man page.
./scripts/build-deb.sh # bumps the PATCH version, then builds the deb
./scripts/build-deb.sh --no-bump # build the current VERSION (test builds)
./scripts/build-deb.sh 2.0.0 # set an exact version, then build
scripts/bump-version.sh minor # or major / patch / X.Y.Z, without buildingSo each released deb bumps the patch automatically; minor and major are
deliberate (bump-version.sh, or pass X.Y.Z). The SBOM reads VERSION, so it
stays in step. Commit the version bump (VERSION + the stamped files) with the
release.
Every document starts with YAML front matter:
---
title: "Document Title"
subtitle: "Document Subtitle"
brand: plain
---Then write Markdown. The house conventions - definition lists over bold labels,
the ::: callout boxes, datatable and chart blocks, footnote citations,
British English - are documented with rendered examples in
pandoc/documentation/Markdown-authoring-guide.md.
The same pipeline produces four kinds of document. A document chooses its format
with the template: field in its front matter (a brand sets the default):
| Format | template: |
What it is |
|---|---|---|
| Report (default) | eisvogel-wrapper (or mvp) |
Title page, TOC, styled body - briefings, policy docs, reports. |
| Featured | featured |
A designed graphical cover (brand-colour band, logo, "Document overview" panel, classification chip, circular cover image, decorative accents) then a section-based body - proposals and client-facing reports. |
| Letter | letter |
No title page; recipient address positioned for a DL window envelope, date and our-ref/your-ref, optional letterhead (full-page artwork or a built logo + contact footer). |
| Slides (beamer) | beamer |
A beamer slide deck; standard beamer theme/colortheme with the brand palette wired in. |
| Slides (modern) | slides |
A flat, full-bleed slide deck (pure xelatex): solid brand-colour grounds, bold type, per-slide colour roles, auto-boxed images. |
All four share the brand system (colours, fonts, identity). Each format's fields
and examples are in pandoc/documentation/Markdown-authoring-guide.md (see the
Letters and Slides sections); man md-to-pdf lists them too.
pandoc/skills/ packages the house format as a skill for Claude Code and for
claude.ai. With it installed, Claude writes Markdown that already follows the
conventions and builds without fixing up - useful for anyone producing documents
for this pipeline. Install/upload instructions are in pandoc/skills/README.md.
plain is the bundled default and the brand to copy when making a new one. Your
organisation brands live outside this repo, in a base folder you manage
(its own repo or a synced folder), pointed to by a config file:
# ~/.config/pandoc-wrapper/config
brands_dir = /path/to/your/brandsResolution is: MD_TO_PDF_BRANDS → brands_dir in the config → the bundled
default. Run md-to-pdf --help or man md-to-pdf for the full mechanism. To
start a new brand:
cp -r /usr/share/pandoc-wrapper/brands/plain ~/your-brands/acme
# edit acme/template.yaml; drop acme/logo.png and acme/cover.pdf inSee pandoc/brands/plain/README.md for the brand folder layout.
md-to-pdf.sh the driver (Bash)
man/md-to-pdf.1 man page
sbom.json CycloneDX SBOM (generated by tools/make-sbom.pl)
scripts/ extract-frontmatter.pl, install.sh, build-deb.sh
tools/ make-sbom.pl + sbom-config.json
pandoc/
├── templates/ eisvogel-wrapper, mvp, pipeline-preamble,
│ │ document-filters.lua, conformance-test.md
│ └── vendor/ pristine upstream Eisvogel (provenance)
├── brands/plain/ the bundled default brand (copy-me reference)
├── skills/ the pandoc-markdown skill (claude.ai + Claude Code)
└── documentation/
├── Markdown-authoring-guide.md how to write documents
├── TEMPLATE-CONTRACT.md how to write a compatible template
└── document-filters-README.md the Lua filter internals
sbom.json is a CycloneDX 1.6 Software Bill of Materials listing the project's
own files (with SHA-256 hashes and licences), the vendored/derived Eisvogel
template, and the runtime dependencies (pandoc, the TeX Live sets, YAML::XS).
Regenerate it after changing what ships:
perl tools/make-sbom.pl # edit tools/sbom-config.json to add componentsThe .deb ships a freshly generated copy at
/usr/share/doc/pandoc-wrapper/sbom.json.
The pipeline is stable and packaged. Outstanding/forward-looking work (document
versioning, a slides template, automated tests, driver hardening) is tracked in
RECOMMENDATIONS.md.
The tool and the bundled plain brand are BSD-3-Clause - see LICENSE.
The bundled templates derive from BSD-3-Clause upstreams whose copyright notices
are retained: eisvogel-wrapper.latex from Eisvogel (© Pascal Wagler, John
MacFarlane; pristine copy under pandoc/templates/vendor/), and mvp.latex from
the pandoc default template (© John MacFarlane; dual GPL-2+/BSD-3-Clause, used
under BSD-3). Pandoc and TeX Live are runtime dependencies, not bundled.
Organisation brands are not covered by this licence: they contain logos and brand identities owned by the respective organisations and are managed in a separate, private repository (all rights reserved).