Pandoc Lua filter that inserts front and back matter pages from format-specific
template files. Template files can reference any YAML metadata field using
$field$ syntax.
Works standalone or combined with dc-mapper.lua (run dc-mapper first).
Pandoc's --include-before-body (-B) and --include-in-header options
insert raw file content without expanding any variables
(issue #812, open since 2014).
A copyright page or dedication included with -B cannot use $title$,
$author$, or any other metadata field — it arrives as literal text.
The canonical workaround suggested by jgm is a two-pass workflow: the snippet is first processed as a Pandoc template to expand variables, and the resulting output is then included in the main document:
pandoc -H <(pandoc --template=copyright.tex input.md) input.md -o output.pdfThis approach leverages the full Pandoc template engine, including conditionals
and loops (
However, it requires shell-specific features such as process substitution or temporary files, which reduces portability in environments like Windows, editors, or CI systems.
This filter provides a lightweight, single-pass alternative for simple
metadata substitution. It avoids shell dependencies by performing minimal
variable expansion (
Pandoc >= 2.19.1
# standalone
pandoc --lua-filter=frontmatter.lua input.md -o output.pdf
# combined with dc-mapper
pandoc --lua-filter=dc-mapper.lua \
--lua-filter=frontmatter.lua \
input.md -o output.pdffrontmatter:
- templates/dedication
- templates/copyright
backmatter:
- templates/colophonfrontmatter and backmatter accept a single string or a list:
frontmatter: templates/dedicationEach entry is a path without extension. The format extension is appended automatically.
| Format | Extension |
|---|---|
| latex | .tex |
| context | .ctx |
| typst | .typ |
| html | .html |
Not supported: docx (Pandoc's docx writer does not support raw fragment insertion).
Templates for unsupported formats are skipped with a warning to stderr.
Any YAML metadata field is available as $field$:
$title$, $author$, $date$
Nested fields use dot notation:
copyright:
- owner: Jane Doe
year: 2026$copyright.owner$, $copyright.year$
For lists of maps, the shortcut without index joins all values with , , and
all items are accessible by index:
creator:
- role: author
text: Jane Doe
- role: editor
text: John Smith$creator.text$ → "Jane Doe, John Smith" ← all values joined with ', '
$creator.1.text$ → "Jane Doe"
$creator.2.text$ → "John Smith"
If you need multiple structured identifiers (e.g. ISBN and DOI), run
dc-mapper.lua first — it maps them to flat variables $isbn$ and $doi$
before frontmatter.lua runs.
Scalar lists are joined with , :
author:
- Jane Doe
- John Smith$author$ → "Jane Doe, John Smith"
Special characters: values are rendered through the Pandoc writer, so
characters with special meaning in the output format are escaped automatically.
For example, 50% in a LaTeX template becomes 50\% (valid LaTeX that renders
as 50% in the PDF).
Missing variables: if a placeholder has no matching variable it is removed from the output and a warning is emitted to stderr:
[frontmatter] warning: variable not found: "$isbn$" in "templates/copyright.tex"
Note: substitution is not recursive — a substituted value containing
$key$ will not be expanded further.
Inline formatting in metadata fields (e.g. dedication: "A mi *querida* familia")
is rendered to format-native markup — \emph{} in LaTeX, <em> in HTML.
Block-level fields (multi-paragraph abstract, etc.) are also rendered natively,
preserving paragraph breaks and inline formatting. Typographic characters
(smart quotes, em-dashes) are preserved in both cases.
Math and special syntax: placeholder cleanup only matches $identifier$ patterns
(starting with a letter or _, followed by alphanumerics, ., or _). Math
expressions such as $E = mc^2$ or Typst math are left untouched.
When combined with dc-mapper.lua, normalized variables like $author$,
$title$, $isbn$, $abstract$ are available directly.
Templates are searched in PANDOC_STATE.resource_path (default: .).
Extend via --resource-path or defaults.yaml:
pandoc --resource-path=.:templates --lua-filter=frontmatter.lua ...resource-path:
- .
- /path/to/shared/templatesThe first matching file is used. If not found, a warning lists the directories searched:
[frontmatter] warning: template not found: "templates/dedication.tex"
(searched: ., /path/to/shared/templates)
| Message | Meaning |
|---|---|
[frontmatter] warning: variable not found: "$X$" in "Y" |
Placeholder $X$ has no matching variable — removed from output. |
[frontmatter] warning: template not found: "X" (searched: Y) |
Template file not found in any resource-path directory. |
[frontmatter] warning: unsupported format "X", skipping "Y" |
Output format has no template extension mapping. |
title: The Art of Typesetting
copyright:
- owner: Jane Doe
year: 2026
publisher: Editorial Example
rights: "© 2026 Jane Doe. All rights reserved."
frontmatter:
- templates/dedication
- templates/copyright
backmatter:
- templates/colophontemplates/copyright.tex:
\begin{flushleft}
\textit{$title$} \\[0.5em]
Copyright \copyright\ $copyright.year$ $copyright.owner$ \\[0.5em]
$publisher$
$rights$
\end{flushleft}
\clearpagetemplates/copyright.html:
<div class="copyright">
<p><em>$title$</em></p>
<p>Copyright © $copyright.year$ $copyright.owner$</p>
<p>$publisher$</p>
<p>$rights$</p>
</div>make test # run all tests
make generate-tests # regenerate expected output from current filter| File | What it covers |
|---|---|
basic.md |
frontmatter + backmatter insertion in latex |
single-string.md |
frontmatter and backmatter as a single string |
nested.md |
nested map access ( |
multi-author.md |
scalar list joined with ', ' |
missing-template.md |
warning emitted to stderr when template not found |
utf8.md |
UTF-8 content in template variables |
percent.md |
% character in variable values |
html.md |
html format — uses .html template extension |
math.md |
math expressions in templates ( |
indexed-list.md |
indexed list access ( |
multi-map.md |
shortcut joins all values when multiple items share the same field key ( |
blocks.md |
block-level metadata rendered natively (paragraph breaks and bold preserved) |
dc-mapper.lua — maps Dublin Core /
EPUB structured metadata to simple Pandoc variables. When used together, run
dc-mapper.lua first so that variables like $title$, $author$, $isbn$
are available to frontmatter.lua templates.