Jesse A. Green / notebook

Pandoc Admin & Developer Guide

Generated June 2026 from jgm/pandoc at 9376458

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

This guide is for the people who build, deploy, embed, and extend pandoc rather than simply run it: packagers, integrators who call pandoc from a service, developers writing readers/writers/filters, and operators who run pandoc-server or the WebAssembly build. It documents pandoc's architecture, build system, configuration surface, security posture, and extension points.

For day-to-day command-line usage, see the companion User Guide. This document assumes you are comfortable with the command line, a Haskell toolchain, and document-conversion concepts.

Note

The pandoc library is at version 3.10 (GPL-2.0-or-later). The repository is a multi-package Haskell project: the core library plus three companion packages (pandoc-cli, pandoc-server, pandoc-lua-engine) and two vendored libraries (citeproc, xml-light).


1. Executive Summary

Pandoc is a Haskell library and command-line tool that converts documents between dozens of markup and word-processing formats. Its defining architectural idea is a single intermediate representation — the Pandoc abstract syntax tree (AST) — that decouples input parsing from output rendering. Every input format has a reader that produces the AST; every output format has a writer that consumes it. As a result, supporting N input and M output formats requires N + M components, not N × M converters.

Attribute Value
Language Haskell
Library version 3.10
License GPL-2.0-or-later
Build system Cabal (cabal-version: 2.4), also Stack
Tested compilers GHC 9.6.7, 9.8.4, 9.10.3, 9.12.2
Packages in repo pandoc, pandoc-cli, pandoc-server, pandoc-lua-engine (+ vendored citeproc, xml-light)
Core library modules ~200 under Text.Pandoc.*
Runtime model Pure conversion pipeline; no daemon required (optional HTTP server)
Scripting Embedded Lua engine for filters and custom readers/writers

Important

Pandoc itself does not generate PDFs. PDF output is delegated to an external engine (LaTeX, ConTeXt, groff, typst, or an HTML-to-PDF tool). Any deployment that needs PDF must provision that engine separately.


2. Architecture

2.1 The conversion pipeline

   ┌──────────┐   ┌──────────┐   ┌─────────────────┐   ┌──────────┐   ┌──────────┐
   │  INPUT   │→ │  READER  │→ │   Pandoc AST    │→ │  WRITER  │→ │  OUTPUT  │
   │ (bytes/  │   │ (parse)  │   │  Blocks +       │   │ (render) │   │ (bytes/  │
   │  text)   │   │          │   │  Inlines + Meta │   │          │   │  text)   │
   └──────────┘   └──────────┘   └────────┬────────┘   └──────────┘   └──────────┘
                                          │
                            ┌─────────────┴─────────────┐
                            │  TRANSFORMS / FILTERS      │
                            │  • Lua filters             │
                            │  • JSON filters (exec)     │
                            │  • citeproc (citations)    │
                            │  • built-in transforms     │
                            └────────────────────────────┘

The AST (Pandoc Meta [Block]) is a list of block-level elements (paragraphs, headings, lists, tables, code blocks) containing inline elements (text runs, emphasis, links, math), plus a metadata map. Readers, writers, and filters all speak this one type. The pipeline is a pure function from input bytes to output bytes, wrapped in pandoc's PandocMonad for controlled IO (file reads, HTTP fetches, logging).

Note

Filters, Lua filters, and citation processing are applied in the exact order they appear on the command line. The order is significant when transforms depend on one another's output.

2.2 Package layout

The repository is split into cooperating packages (declared in cabal.project):

Package Directory Role
pandoc (library) src/ The core library: readers, writers, the AST, the App driver, templates, highlighting glue
pandoc-cli pandoc-cli/ The pandoc executable; wires the library to a command line; can also run as server or REPL
pandoc-server pandoc-server/ An HTTP conversion service built on Servant/WAI (version 0.1.3)
pandoc-lua-engine pandoc-lua-engine/ The Lua scripting engine for filters and custom readers/writers
citeproc citeproc/ Vendored Citation Style Language (CSL) processor
xml-light xml-light/ Vendored lightweight XML parser used by XML-based readers/writers

This separation lets downstream users depend on only what they need — for example, a service that converts in-process can depend on the pandoc library and pandoc-lua-engine without pulling in the CLI or the HTTP server.

2.3 The CLI driver

The command-line entry point lives in Text.Pandoc.App (src/Text/Pandoc/App.hs). Its public surface includes:

Symbol Purpose
convertWithOpts :: Opt -> PandocIO () Run a full conversion from a parsed options record
Opt(..) The complete set of conversion options (every CLI flag has a field)
options The option parser specification (GetOpt descriptors)
parseOptions / parseOptionsFromArgs Parse argv into an Opt
applyFilters Apply the configured filter chain to the AST
defaultOpts The default option record
versionInfo The --version banner contents

The actual flag parsing and the Opt record are factored into the Text.Pandoc.App.Opt and Text.Pandoc.App.CommandLineOptions submodules under src/Text/Pandoc/App/.


3. Repository Structure

pandoc/
├── src/Text/Pandoc/          Core library (~200 modules)
│   ├── App.hs                CLI driver: convertWithOpts, options
│   ├── App/                  Option record + command-line parsing
│   ├── Readers/              One module per input format (Markdown, LaTeX,
│   │                         Docx, ODT, HTML, Org, RST, Typst, …)
│   ├── Writers/              One module per output format (HTML, LaTeX,
│   │                         Docx, Powerpoint, Markdown, JATS, …)
│   ├── Parsing/              Shared parser combinators
│   ├── Citeproc/             Bridge to the citeproc library
│   ├── Class/                PandocMonad: controlled IO abstraction
│   └── Data/                 Bundled data-file access
├── pandoc-cli/               The pandoc executable
│   ├── src/                  Main entry, dispatch to server/lua/repl
│   └── (build variants: server, no-server, lua, no-lua, wasm)
├── pandoc-server/            HTTP server (Servant/WAI)
├── pandoc-lua-engine/        Lua filter & scripting engine
├── citeproc/                 Vendored CSL processor + localizations
├── xml-light/                Vendored XML parser
├── data/                     Templates, translations, reference docs
│   ├── templates/            Default templates per output format
│   ├── translations/         UI-string localizations (107 locales)
│   ├── reference.docx/.odt/.pptx   Style reference documents
│   └── *.lua, abbreviations, init.lua
├── man/                      Man-page sources
├── wasm/                     WebAssembly build (JS glue, examples, patches)
├── test/                     Test suite (command tests, golden files)
├── benchmark/                Performance benchmarks
├── linux/ macos/ windows/    Platform packaging scripts
├── pandoc.cabal              Library + test + benchmark definitions
├── cabal.project             Multi-package + wasm build configuration
├── stack.yaml                Stack build configuration
└── MANUAL.txt                The authoritative user manual (source of MANUAL.html)

3.1 The data/ directory

Everything in data/ ships with pandoc and is discoverable at runtime through the user data directory or (when compiled with embed_data_files) baked into the binary:

  • templates/ — the default standalone template for each output format. Inspect any with pandoc -D FORMAT.
  • translations/ — localized UI strings (e.g. "Figure", "Table of Contents") for 107 locales, selected via the lang variable.
  • reference.docx / reference.odt / reference.pptx — the style templates for Office output. Extract with pandoc --print-default-data-file reference.docx.
  • init.lua — the Lua init script run when the scripting engine starts.

4. System Requirements

4.1 Runtime

The released binaries are statically linked and have no dynamic dependencies or external data-file requirements. The only optional runtime dependencies are the external tools pandoc shells out to:

Capability External requirement
PDF via LaTeX A TeX distribution: TeX Live, MiKTeX, BasicTeX, or TinyTeX
PDF via HTML weasyprint, prince, wkhtmltopdf, or pagedjs-cli
PDF via ConTeXt / ms / typst context, groff/pdfroff, or typst
SVG in formats without SVG support rsvg-convert (from librsvg)
JSON filters The interpreter for the filter's language (e.g. Python)

Warning

The statically-linked binaries (and Conda Forge builds) cannot load Lua filters that depend on C-based Lua modules. Deployments needing those must use a dynamically-linked build compiled from source.

4.2 Build toolchain

Tool Minimum
GHC 9.6.7 (tested up to 9.12.2)
Cabal 2.0+ (cabal-version: 2.4 in the project)
Stack 1.7.0+ (alternative to Cabal)

5. Building From Source

Build the executable, not the bare library, by targeting pandoc-cli.

5.1 Stack

git clone https://github.com/jgm/pandoc
cd pandoc
stack setup            # downloads the right GHC if needed
stack install pandoc-cli

stack install places the pandoc binary in ~/.local/bin; add it to your PATH.

5.2 Cabal

cabal update
cabal install pandoc-cli      # released version from Hackage

To build the local (development) tree instead, run the command from inside the source directory — cabal uses the cabal.project packages.

5.3 Custom build (for packagers)

cabal update
cabal build --only-dependencies
cabal configure --flags=FLAGSPEC --enable-tests
cabal build
cabal test
cabal install pandoc-cli

5.4 Build flags

Flags are toggled with +flag / -flag in cabal configure --flags=..., in cabal.project's package stanzas, or with --flag pandoc:embed_data_files under Stack.

pandoc library flags:

Flag Default Effect
embed_data_files off Bake all data/ files into the binary, producing a relocatable, self-contained executable
http on Include HTTP support for fetching remote inputs/resources

pandoc-cli flags:

Flag Default Effect
lua on Compile in Lua filter and custom-writer support (links pandoc-lua-engine)
server on Compile in HTTP server mode (active when the binary is named/symlinked pandoc-server)
repl on Compile in the Lua REPL
nightly off Stamp the build with nightly version metadata

Tip

For a fully relocatable binary, build with embed_data_files: cabal build -fembed_data_files or stack build --flag pandoc:embed_data_files. This is also the recommended build for running under --sandbox, since data-file lookups then succeed without filesystem access.

5.5 Running tests and benchmarks

cabal test                                   # full suite
cabal test --test-options='-p markdown'      # only tests matching "markdown"
cabal test --test-options='-j4 --hide-successes'
cabal configure --enable-benchmarks && cabal build
cabal bench
cabal bench --benchmark-options='markdown -s 20'

The test driver is test/test-pandoc.hs. Format conversions are largely tested via command tests (test/command/) — each file pairs an invocation with its expected output — and golden files under format-specific subdirectories (test/docx/, test/odt/, test/pptx/, etc.).


6. Embedding the Library

To convert documents in-process from Haskell, depend on the pandoc library and use the high-level API from the top-level Text.Pandoc module. A conversion runs inside PandocMonad (typically runIO):

import Text.Pandoc

main :: IO ()
main = do
  result <- runIO $ do
    doc <- readMarkdown def "# Hello *world*"
    writeHtml5String def doc
  html <- handleError result
  putStrLn (show html)

Key library entry points:

Module Provides
Text.Pandoc Re-exports readers, writers, options, and the monad
Text.Pandoc.Readers.* readMarkdown, readLaTeX, readDocx, …
Text.Pandoc.Writers.* writeHtml5String, writeLaTeX, writeDocx, …
Text.Pandoc.Class PandocMonad, runIO, runPure (sandboxed, no IO)
Text.Pandoc.App convertWithOpts for a full CLI-equivalent run
Text.Pandoc.JSON toJSONFilter for building JSON filters in Haskell

Note

runPure runs a conversion with no IO at all — useful for safely converting untrusted input or for testing. runIO permits controlled IO (file reads, HTTP, logging) mediated by PandocMonad.


7. Deploying pandoc-server

The pandoc-server package (version 0.1.3) exposes pandoc conversion over HTTP using the Servant framework on WAI. The same functionality is built into the pandoc binary when compiled with the server flag and is activated by invoking the binary as pandoc-server.

7.1 Server options

From Text.Pandoc.Server (pandoc-server/src/Text/Pandoc/Server.hs):

Option Flag Default Meaning
Port -p / --port 3030 TCP port to listen on
Timeout -t / --timeout 2 Per-request timeout in seconds
# Run the server (binary built/symlinked as pandoc-server)
pandoc-server --port 3030 --timeout 5

The server defines a Servant API and a WAI app. Requests carry a JSON Params body describing the conversion (input text or base64 Blob, source/target formats, options); the response is the converted document.

7.2 Operational characteristics

  • Stateless. Each request is an independent pure conversion. Scale horizontally behind a load balancer; no shared state or database.
  • CORS. The server installs WAI CORS middleware, so browser clients can call it cross-origin.
  • Short default timeout. The 2-second default protects against pathological inputs; raise it deliberately for large documents.

Caution

A conversion server accepts arbitrary documents from clients. Treat every request as untrusted input. The server performs conversions in a constrained manner, but you remain responsible for resource limits (request size, concurrency, timeout) and for not enabling filters or PDF engines that reach the network or filesystem on a public endpoint. See section 9.


8. WebAssembly Build

Pandoc compiles to WebAssembly (wasm32) for in-browser conversion, powering the online demo. The wasm configuration lives in cabal.project (guarded by if arch(wasm32)) and the wasm/ directory.

Notable wasm build choices:

  • Tests are disabled (tests: False).
  • The CLI is built with +lua -repl -server and the library with +embed_data_files -http — no HTTP, no server, but Lua filters and embedded data are retained.
  • Several dependencies are pulled from patched Git forks (wasm/patches/*.patch) to work under the WASI/wasm32 toolchain (conduit-extra, streaming-commons, xml-conduit, splitmix, hslua, and a custom Lua build with stubbed tmpnam/tmpfile/system).
  • wasm/index.js provides the JavaScript glue; wasm/examples/ contains ready-made conversion demos (e.g. markdown-to-revealjs, docx-with-equations-to-latex).

Note

The wasm build is a specialized target. Most integrators want the native library or pandoc-server instead; reach for wasm only when conversion must run client-side in a browser sandbox.


9. Security Analysis

Pandoc's threat model centers on processing untrusted documents. The relevant controls:

9.1 Sandbox mode

--sandbox (or sandbox: true in a defaults file; runPure in the library) limits the IO that readers and writers may perform to the files named on the command line. This blocks attacks such as exfiltrating local files through include directives in the source document.

pandoc --sandbox untrusted.md -o out.html

Caution

--sandbox does not constrain:

  • Filters — JSON and Lua filters run with full process privileges. Do not enable user-supplied filters on untrusted input.
  • PDF engines — the external engine runs unsandboxed. Generating PDFs from user-controlled HTML can enable server-side request forgery (SSRF) through the engine's resource fetching. If you must do this, isolate the engine (no network, restricted filesystem) at the OS/container level.

9.2 Network access

Pandoc fetches remote inputs and resources over HTTP when given URLs or when --embed-resources downloads assets. On a server or for untrusted input, consider building with -http (disabling HTTP support) or running in --sandbox, and restrict egress at the network layer. --no-check-certificate disables TLS verification — never enable it for untrusted or production fetches.

9.3 Data-file lookups under sandbox

Some readers/writers (notably docx) need bundled data files. Under --sandbox, filesystem lookups fail. Compile with embed_data_files so the data is baked into the binary and the sandbox still works.

9.4 Reporting

Security issues are reported privately per SECURITY.md in the repository, not through public issues.

9.5 Failure modes as signals

Pandoc fails closed with a specific nonzero exit code (see section 12). In automation, treat any nonzero exit as a failed conversion and inspect the code — for example, code 47 (PDF program not found) versus 64 (parse error) call for very different responses.


10. Configuration Surface

Pandoc has no central config file; configuration is layered from three sources, later sources overriding earlier ones:

  1. The user data directory — bundled and user-overridable resources.
  2. Defaults files — reusable YAML/JSON option sets (-d/--defaults).
  3. Command-line options and document metadata — highest precedence.

10.1 The user data directory

Resources placed here override pandoc's built-in defaults. Default locations:

Platform Default user data directory
*nix / macOS $XDG_DATA_HOME/pandoc (i.e. $HOME/.local/share/pandoc), falling back to $HOME/.pandoc
Windows %APPDATA%\pandoc

Override per run with --data-dir. Confirm the active directory with pandoc --version. Recognized subdirectories and files include templates/, defaults/, filters/, metadata/, csl/, and override copies of reference.docx, reference.odt, epub.css, and abbreviations.

Note

Pandoc does not create the user data directory for you. Create it yourself before dropping in overrides.

10.2 Defaults files

A defaults file maps almost every command-line option to a YAML key. Store frequently used option sets and invoke them by name:

# ~/.local/share/pandoc/defaults/report.yaml
from: markdown
to: pdf
pdf-engine: xelatex
standalone: true
number-sections: true
toc: true
toc-depth: 2
variables:
  geometry: margin=1in
  fontsize: 11pt
verbosity: INFO
pandoc input.md -dreport -o report.pdf

Path fields support ${HOME}, ${USERDATA}, and ${.} (the defaults file's own directory) interpolation. Repeatable options (--css, --metadata-file, --include-in-header, --variable, …) combine with, rather than replace, command-line values.

10.3 Templates and reference documents

  • Templates drive standalone output. Print the default with pandoc -D FORMAT; override by placing a file in templates/ or passing --template.
  • Reference documents (reference.docx, reference.odt, reference.pptx) drive the styling of Office output. Extract the defaults with --print-default-data-file, edit the styles, and supply with --reference-doc.

11. Extending Pandoc

11.1 Filters

The lowest-friction extension point. A filter transforms the AST between reading and writing:

  • Lua filters (-L/--lua-filter) run on the built-in engine; no external dependency. Each filter maps AST element names to transforming functions.
  • JSON filters (-F/--filter) are external executables reading/writing the AST as JSON. Writable in any language; the target format is passed as argv[1]. In Haskell, Text.Pandoc.JSON.toJSONFilter builds one.

Filter lookup order: explicit path → $DATADIR/filters → $PATH (executables only).

11.2 Custom readers and writers

Beyond the built-in formats, pandoc can load a custom reader or writer written in Lua by passing the script path as the -f/-t format. This lets you support a new format without recompiling, by implementing the parse or render logic in Lua against pandoc's AST.

11.3 Adding a built-in format

To add a first-class format, add a module under src/Text/Pandoc/Readers/ or Writers/, wire it into the format registry, and add tests (golden files under test/ and/or command tests under test/command/). Each reader produces the AST; each writer consumes it — the modular design means no other component changes.


12. Exit Codes Reference

Pandoc returns 0 on success and a specific code on failure. Use these in automation to distinguish failure classes:

Code Constructor Code Constructor
1 PandocIOError 47 PandocPDFProgramNotFoundError
3 PandocFailOnWarningError 61 PandocHttpError
4 PandocAppError 62 PandocShouldNeverHappenError
5 PandocTemplateError 63 PandocSomeError
6 PandocOptionError 64 PandocParseError
21 PandocUnknownReaderError 66 PandocMakePDFError
22 PandocUnknownWriterError 67 PandocSyntaxMapError
23 PandocUnsupportedExtensionError 83 PandocFilterError
24 PandocCiteprocError 84 PandocLuaError
25 PandocBibliographyError 89 PandocNoScriptingEngine
31 PandocEpubSubdirectoryError 91 PandocMacroLoop
43 PandocPDFError 92 PandocUTF8DecodingError
44 PandocXMLError 93 PandocIpynbDecodingError
— — 99 PandocResourceNotFound

13. Operations Playbook

13.1 Verifying a deployment

pandoc --version                       # version, features, data dir
pandoc --list-input-formats | wc -l    # reader count sanity check
echo '# ok' | pandoc -t html           # smoke-test a conversion

13.2 Provisioning for PDF

Confirm the chosen engine is on PATH before enabling PDF jobs. For LaTeX, install missing packages with your TeX manager (e.g. tlmgr install collection-fontsrecommended). For HTML-based PDF, install WeasyPrint/Prince/wkhtmltopdf and select it with --pdf-engine.

13.3 CI integration

  • Use --fail-if-warnings to make conversions fail loudly (exit code 3).
  • Use --log=FILE to capture machine-readable JSON logs for build artifacts.
  • Pin a pandoc version; behavior and default extensions evolve between releases.
  • Official Docker images (pandoc/core, pandoc/latex) and GitHub Actions / GitLab CI examples exist for pipeline use.

13.4 Performance

Released binaries are compiled with -threaded and the RTS option -A8m (8 MB allocation area). For very large documents, the dominant cost is usually parsing and the external PDF engine, not pandoc's rendering. Benchmark with the in-repo cabal bench suite when evaluating regressions.


14. Code Quality & Conventions

  • Style. The project enforces formatting with stylish-haskell (.stylish-haskell.yaml) and lint rules with HLint (.hlint.yaml); .editorconfig standardizes whitespace.
  • Dead-code detection. weeder.toml configures Weeder to find unused exports.
  • Module discipline. One reader/writer per format keeps the surface area predictable; shared logic lives in Parsing/, Writers/Shared, and Readers/-level helpers.
  • Controlled IO. All IO flows through PandocMonad, enabling both real-IO (runIO) and pure (runPure) execution from the same code — central to sandboxing and testability.
  • Testing strategy. Heavy use of golden tests and declarative command tests (test/command/) means most format behavior is pinned to expected output, making regressions easy to catch.

15. Potential Improvements

Drawn from the project's open issue tracker at the documented commit — these are active areas, useful context for contributors and integrators:

Theme Representative open issues
Typst writer fidelity lang attribute propagation on spans (#11673); EPUB→PDF via the typst engine (#11626)
Org mode reader Use code (~) vs verbatim (=) markers (#11655)
New format support Atlassian Document Format (ADF) reader/writer for Confluence Cloud (#9898)
Markdown lists Mixing incremental and non-incremental list types (#11653)
HTML output Lazy-loading images (loading="lazy") (#6197)
Word/ODT styling List paragraphs using "List Paragraph" vs "Normal" style (#10974)

Tip

The modular reader/writer architecture means most of these are localized changes — a single writer module plus tests — rather than cross-cutting work. The format registry and the golden-test layout are the two touch points a contributor must learn first.


16. Quick Reference

Build

Task Command
Build with Stack stack install pandoc-cli
Build with Cabal cabal install pandoc-cli
Relocatable binary cabal build -fembed_data_files
Run tests cabal test
Run a subset of tests cabal test --test-options='-p PATTERN'
Run benchmarks cabal bench

Server

Task Command
Start server pandoc-server --port 3030 --timeout 5
Default port / timeout 3030 / 2s

Configuration

Item Where
User data dir (*nix) $HOME/.local/share/pandoc
User data dir (Windows) %APPDATA%\pandoc
Override data dir --data-dir=DIR
Defaults files <data-dir>/defaults/*.yaml, used via -dNAME
Inspect default template pandoc -D FORMAT
Extract a data file pandoc --print-default-data-file=FILE

Security

Goal Action
Process untrusted input --sandbox (+ embed_data_files build)
Disable HTTP support build with -http
Make warnings fatal --fail-if-warnings
Pure, no-IO conversion (library) runPure