Lua in documents
A document can run Lua without being a Lua script. Markdown and HTML both load a companion file, and Markdown adds Lua blocks and inline expressions on top.
| Feature | Markdown | HTML |
|---|---|---|
| Companion Lua file | yes | yes |
| Callbacks | yes | yes |
_aux and _toc |
yes | yes |
_frontmatter |
yes | no |
| Lua blocks | yes | no |
| Inline expressions | yes | no |
Companion Lua file
If a Lua file with the same base name as the document exists, glu loads it automatically before it processes the document. This is the place for function definitions, variable initialization and callback registration.
report.md ← your document
report.lua ← loaded automatically (if present)
page.html ← your document
page.lua ← loaded automatically (if present)Example report.lua:
function currency(amount)
return string.format("%.2f €", amount)
end
total = 42 * 3In Markdown, the functions and variables are available in Lua blocks and inline expressions. HTML has neither, so there the companion file mostly registers callbacks, for page backgrounds, overlays or element processing:
local frontend = require("glu.frontend")
frontend.add_callback("page_init", "bg", function(doc, page, pagenum, pageinfo)
-- draw page background
end)Frontmatter fields
All fields of the frontmatter of a Markdown document, including custom ones, are available as the _frontmatter global table. HTML documents have no frontmatter and no such table.
-- Access standard fields
local title = _frontmatter.title
-- Access custom fields
local version = _frontmatter.versionThis means you can add arbitrary key-value pairs to the frontmatter and read them from Lua blocks or the companion Lua file.
Lua blocks
Fenced code blocks with {lua} are executed and removed from the output:
```{lua}
result = math.sqrt(144)
```Lua blocks run in the same Lua state as the companion file. They execute in document order, so later blocks can use variables from earlier ones.
Returning text
If a Lua block uses return, the returned string is inserted into the Markdown source at the position of the block:
```{lua}
return '<div class="note">'
```
This paragraph is wrapped in a div.
```{lua}
return '</div>'
```This is useful for injecting HTML tags, dynamic content, or computed Markdown.
Recording and processing text
Two Lua blocks can work together to capture and transform a section of text. startrecording() begins capturing all text between Lua blocks; stoprecording() stops capturing and returns the captured text as a Lua string:
```{lua}
startrecording()
```
This text will be captured and processed.
```{lua}
local content = stoprecording()
return string.upper(content)
```The result is “THIS TEXT WILL BE CAPTURED AND PROCESSED.”: the Markdown between the two blocks was captured, transformed by string.upper(), and re-inserted via return.
Recording works on the raw Markdown text level (before goldmark converts to HTML). This means:
- Markdown formatting (
**bold**,# heading) inside the recorded text is preserved and processed normally after the Lua transformation {= expr =}inline expressions inside recorded text are expanded after the Lua transformation, so if the transformation alters the{= ... =}syntax, the expressions will break
| Function | Description |
|---|---|
startrecording() |
Begins capturing text. Subsequent text goes to the buffer instead of the output. |
stoprecording() |
Stops capturing and returns the captured text as a string. |
Display-only blocks
To show Lua code without executing it, use {!lua} instead of {lua}. The block is rendered with syntax highlighting but not executed:
```{!lua}
-- This code is displayed, not executed
items = { "one", "two", "three" }
return #items
```This is useful for documentation or slides where you want to show Lua examples.
Showing fenced code blocks verbatim
To display a ```{lua} block literally (including the backtick fencing), wrap it with more backticks. Use 5 backticks to protect the inner 3-backtick block from execution:
`````
```{lua}
return "This is shown as code, not executed"
```
`````The outer fence must have strictly more backticks than any inner fence. This works for any number of nesting levels: wrap with 6 backticks to show 5 backticks, and so on.
Inline expressions
Use {= expr =} to insert the result of a Lua expression:
The total is {= total =} and the square root of 144 is {= math.sqrt(144) =}.Any Lua expression that returns a value works, such as variables, function calls and arithmetic:
The price is {= currency(19.99) =}.Accessing aux data from Lua
The auxiliary file is exposed as the _aux global table in Lua. It is readable and writable, and any changes are saved back to the JSON file after the run.
-- Read system data from previous run
if _aux._pages then
print("Last run: " .. _aux._pages .. " pages")
end
-- Read headings (also available as _toc for backward compatibility)
if _toc then
for _, h in ipairs(_toc) do
print(h.level .. ": " .. h.text .. " (page " .. h.page .. ")")
end
end
-- Store custom data that persists across runs
_aux.build_count = (_aux.build_count or 0) + 1
_aux.my_keywords = { "typesetting", "PDF", "Lua" }The _toc global is an alias for _aux._headings and remains available for backward compatibility.
System keys (with _ prefix) are updated by glu after each run. You can read and even overwrite them, but glu will set _pages and _headings to the actual values at the end of the run. To store persistent custom data, use keys without underscore prefix.
The lifecycle callbacks document_start, content_ready, and document_end provide precise control over when _aux is read or modified. Modifications made in document_end are included when the aux file is written.
Complete example
report.md:
---
title: Monthly Report
author: Jane Doe
---
# Monthly Report
This report covers the period from January to March.
| Month | Revenue | Expenses |
|---------|----------|----------|
| January | {= currency(15420) =} | {= currency(12300) =} |
| February| {= currency(18900) =} | {= currency(14100) =} |
| March | {= currency(21050) =} | {= currency(15800) =} |
**Total revenue:** {= currency(15420 + 18900 + 21050) =}
---
> Report generated with glu.report.lua:
function currency(amount)
return string.format("%.2f €", amount)
endRun:
glu report.md # → report.pdf