Folio Writer

Bend the compiler:
pandoc Lua filters in Folio Writer

Folio Writer's compile pipeline covers the 99% — citations, cross-references, templates, formats. Lua filters are the escape hatch for the last 1%: the journal's odd house rule, your own shorthand, the thing no checkbox anticipates. A filter is a few lines of Lua that rewrite the document during compilation, and pandoc runs them natively — nothing to install.

What a filter actually is

When pandoc compiles your manuscript, it first parses the Markdown into a document tree — paragraphs, headers, spans, citations. A Lua filter is a set of functions named after those elements; each one gets called with every matching element and may return a replacement. Return nothing to leave it alone, return {} to delete it, return new elements to transform it. That's the whole model.

Attaching one in Folio Writer

  1. Save your filter as a .lua file — keeping it in the project (e.g. a Filters subfolder in Research) means it syncs and travels with the manuscript.
  2. In the compile sheet's Style tab: Lua filters ▸ Add Lua Filter…. Add several and they run in order, after pandoc-crossref.
  3. Filters are saved with the compile profile — so "Elsevier submission" and "draft for my advisor" can each carry their own set.
The preview plays along: the live preview runs your filters in the same order as compile — so you see the filter's effect while writing, not as a surprise in the PDF.

Example 1 · An always-current date

Type @@today anywhere; the compiled document carries the compile date — useful in cover letters and draft watermarks:

-- today.lua
function Str(el)
  if el.text == "@@today" then
    return pandoc.Str(os.date("%B %e, %Y"))
  end
end

Example 2 · Draft notes that never ship

Write notes-to-self in a fenced div, visible while drafting, guaranteed absent from output:

::: draft
Ask Wei whether the 2019 calibration applies here.
:::
-- strip-draft.lua
function Div(el)
  if el.classes:includes("draft") then
    return {}          -- deleted from every compiled format
  end
end

Attach it only to your submission profiles and the notes still appear in your advisor-draft profile — per-profile filters are exactly for this.

Example 3 · Your own inline markup

Give [key result]{.highlight} a real meaning per format — a yellow mark in HTML, a highlight in LaTeX PDFs:

-- highlight.lua
function Span(el)
  if not el.classes:includes("highlight") then return nil end
  if FORMAT:match("latex") then
    local out = pandoc.Inlines(pandoc.RawInline("latex", "\\hl{"))
    out:extend(el.content)
    out:insert(pandoc.RawInline("latex", "}"))
    return out
  elseif FORMAT:match("html") then
    local out = pandoc.Inlines(pandoc.RawInline("html", ""))
    out:extend(el.content)
    out:insert(pandoc.RawInline("html", ""))
    return out
  end
end

For the LaTeX branch, add the highlighting package once in the Format metadata: header-includes: \usepackage{soul}.

Debugging without guesswork

Two habits make filter-writing painless. To see the tree you're matching against, run pandoc -t native on a snippet in Terminal — it prints exactly the elements your functions will receive. And when a compile misbehaves, Compile & Preview Log shows the full pandoc command line and its output, filter errors included. One Folio-specific note: a filter whose file has moved is skipped rather than failing the compile — check the log if a filter seems silently inactive.

Where to go deeper

The pandoc Lua filter documentation lists every element and helper, and the community's filter collection is full of ready-made ones — abbreviation handling, table tweaks, diagram rendering. Anything there drops straight into the compile sheet.

Download Folio Writer free →  ·  More guides