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
| Phase | YAML key | Runs with | Use for |
|---|---|---|---|
| Parse-time | pandoc.filters.parse | FORMAT == "markdown" | Cheap AST rewrites that should affect Emanote’s model — titles, tags, [[wikilinks |
| Render-time | pandoc.filters.render.html | FORMAT == "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.luaworks when the file exists in your notebook -
lua-filters/list-table.luaandlua-filters/wordcount.luawork 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
-
Filter declarations are note-local. Cascading
pandoc.filtersfrom 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, notpandoc.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 aN words · M charactersfooter. -
diagram.lua— fenced code blocks ford2,cetz, and the other enginespandoc-ext/diagramsupports → inline SVG. The wrapped Emanote binary carriesd2,typst, and an offline@preview/cetzpackage cache; see Diagrams. -
hello.lua— a hello-world filter that drives the writing-filters guide and exercises the error-reporting protocol. Droplua-filters/hello.luainto a note’spandoc.filters.render.htmland fence withhelloblocks to try it.
Local docs filters
This docs notebook also includes a custom filter under docs/filters/:
-
slides.lua— turns a:::slidesdiv into a navigable Markdown presentation; runs the A Markdown Presentation about Lua Filters deck.
Demos
list-table.lua
| row 1, column 1 | row 1, column 2 | row 1, column 3 |
|---|---|---|
| row 2, column 1 | row 2, column 3 | |
| row 3, column 1 | row 3, column 2 | Well! |
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.