Skip to content

plbarrio/frontmatter

Repository files navigation

frontmatter.lua

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).

Problem

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.pdf

This approach leverages the full Pandoc template engine, including conditionals and loops ($if(field)$, $for(list)$, etc.), making it the most expressive solution for complex front and back matter.

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 ($field$ only) directly in Lua, at the cost of not supporting template logic such as conditionals or loops.

Requirements

Pandoc >= 2.19.1

Usage

# 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.pdf

YAML

frontmatter:
  - templates/dedication
  - templates/copyright
backmatter:
  - templates/colophon

frontmatter and backmatter accept a single string or a list:

frontmatter: templates/dedication

Each entry is a path without extension. The format extension is appended automatically.

Supported formats

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.

Template variables

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.

Template search path

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/templates

The 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)

Warnings

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.

Example

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/colophon

templates/copyright.tex:

\begin{flushleft}
\textit{$title$} \\[0.5em]
Copyright \copyright\ $copyright.year$ $copyright.owner$ \\[0.5em]
$publisher$
$rights$
\end{flushleft}
\clearpage

templates/copyright.html:

<div class="copyright">
  <p><em>$title$</em></p>
  <p>Copyright &copy; $copyright.year$ $copyright.owner$</p>
  <p>$publisher$</p>
  <p>$rights$</p>
</div>

Tests

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 ($copyright.owner$, $copyright.year$)
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 ($E = mc^2$) are not eaten by placeholder cleanup
indexed-list.md indexed list access ($creator.1.text$, $creator.2.text$) and shortcut join ($creator.text$ = all values joined)
multi-map.md shortcut joins all values when multiple items share the same field key ($creator.text$ = "Jane, John")
blocks.md block-level metadata rendered natively (paragraph breaks and bold preserved)

Related

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.

About

A Pandoc & Quarto Lua filter to automatically inject dynamic frontmatter and backmatter templates.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors