Pandoc Lua Filters

A Pandoc Lua filter rewrites the parsed Pandoc document before Emanote turns it into an HTML page. Filters are note-local — declare them in Markdown frontmatter, or for Org notes via #+PANDOC_FILTERS_PARSE: / #+PANDOC_FILTERS_RENDER_HTML: keywords.

Choose a phase

PhaseYAML keyRuns withUse for
Parse-timepandoc.filters.parseFORMAT == "markdown"Cheap AST rewrites that should affect Emanote’s model — titles, tags, [[wikilinks
Render-timepandoc.filters.render.htmlFORMAT == "html"HTML-specific output, diagrams, shelling out to engines, anything that should not run when Emanote is only after metadata. IO is allowed.

Both phases use the same filter shape; see Writing a Pandoc Lua filter for the API, the error-reporting protocol, and a hello-world example.

Filter paths

Filter paths are resolved against your notebook layers first, then against Emanote’s default layer:

  • filters/custom.lua works when the file exists in your notebook
  • lua-filters/list-table.lua and lua-filters/wordcount.lua work without copying anything into your notes — they come from the default layer
  • multiple filters run in declaration order

This page chains the bundled list-table.lua and wordcount.lua — the wordcount footer at the bottom proves it.

Org notes

Org notes use one keyword per filter:

#+PANDOC_FILTERS_PARSE: lua-filters/list-table.lua
#+PANDOC_FILTERS_PARSE: lua-filters/wordcount.lua
#+PANDOC_FILTERS_RENDER_HTML: filters/slides.lua

Hot reload

Edits to .lua files hot-reload. The live server re-parses every note that references a changed filter, no touch needed. A filter referenced before it exists on disk re-parses dependent notes the moment the file lands. .lua files also remain linkable as source files — see Embedding.

Limitations

Warning
  • Filter declarations are note-local. Cascading pandoc.filters from an ancestor `index.yaml` is tracked under #263.
  • Filters that need filesystem, process, media, module-loading, or dynamic-code IO belong under pandoc.filters.render.html, not pandoc.filters.parse.

Bundled filters

Four curated filters ship in Emanote’s default layer under emanote/default/lua-filters/:

  • list-table.lua — nested bullet lists → HTML tables. From pandoc-ext/list-table.
  • wordcount.lua — appends a N words · M characters footer.
  • diagram.lua — fenced code blocks for d2, cetz, and the other engines pandoc-ext/diagram supports → inline SVG. The wrapped Emanote binary carries d2, typst, and an offline @preview/cetz package cache; see Diagrams.
  • hello.lua — a hello-world filter that drives the writing-filters guide and exercises the error-reporting protocol. Drop lua-filters/hello.lua into a note’s pandoc.filters.render.html and fence with hello blocks to try it.

Local docs filters

This docs notebook also includes a custom filter under docs/filters/:

Demos

list-table.lua

row 1, column 1row 1, column 2row 1, column 3
row 2, column 1row 2, column 3
row 3, column 1row 3, column 2Well!

wordcount.lua

The footer at the bottom of this page — every save recomputes it.

slides.lua

See A Markdown Presentation about Lua Filters for a Markdown presentation rendered by slides.lua at HTML render time.

diagram.lua

See Diagrams for inline-SVG demos of d2 and cetz fences, both rendered offline.

hello.lua

See Writing a Pandoc Lua filter — embedded source plus live happy and sad-path renders.