\begin{article}
LuaLaTeX via API: Programmable PDFs with Lua Scripting
LuaLaTeX embeds a full Lua interpreter inside LaTeX. Here is what that enables, how to use it via the FormaTeX API, and when it makes sense over XeLaTeX or pdflatex.

LuaLaTeX is the most powerful LaTeX engine, but the least understood. Most developers know pdflatex and XeLaTeX — but LuaLaTeX enables a category of document generation that is simply impossible in the other engines: Lua scripting running directly inside your document during compilation.
What Makes LuaLaTeX Different
Every LaTeX engine compiles markup to PDF. LuaLaTeX adds a full Lua 5.3 interpreter that runs during compilation, with direct access to TeX internals.
This means you can:
- Generate table content programmatically from data structures
- Implement conditional document logic beyond what LaTeX macros support
- Access TeX's internal data (font metrics, box dimensions, etc.) from Lua
- Call external Lua libraries (for string processing, math, etc.)
The Lua code runs on the server during compilation — there is no "Lua in the PDF." The Lua generates LaTeX content, which is then typeset normally.
Lua in LaTeX Documents
The primary interface is \directlua{}:
\documentclass{article}
\usepackage{luacode}
\begin{document}
\section{Programmatic Content}
% Fibonacci sequence generated by Lua
Fibonacci numbers: \directlua{
local a, b = 0, 1
local result = {}
for i = 1, 10 do
table.insert(result, a)
a, b = b, a + b
end
tex.sprint(table.concat(result, ", "))
}
\end{document}The luacode package provides cleaner environments for longer scripts:
\begin{luacode}
-- This block has no escaping concerns
local data = {
{name = "Alice", score = 95},
{name = "Bob", score = 87},
{name = "Carol", score = 92},
}
-- Sort by score descending
table.sort(data, function(a, b) return a.score > b.score end)
-- Output as LaTeX table rows
for rank, student in ipairs(data) do
tex.sprint(rank .. " & " .. student.name .. " & " .. student.score .. " \\\\")
end
\end{luacode}Use Cases
Data-Driven Tables
The most common LuaLaTeX use case — generate table rows from Lua data instead of hardcoding:
\documentclass{article}
\usepackage{luacode}
\usepackage{booktabs}
\begin{document}
\begin{tabular}{lrr}
\toprule
Product & Units & Revenue \\
\midrule
\begin{luacode}
local products = {
{name = "Widget A", units = 1240, price = 29.99},
{name = "Widget B", units = 890, price = 49.99},
{name = "Service X", units = 340, price = 199.00},
}
local total_revenue = 0
for _, p in ipairs(products) do
local revenue = p.units * p.price
total_revenue = total_revenue + revenue
tex.sprint(string.format(
"%s & %d & \\$%.2f \\\\", p.name, p.units, revenue
))
end
tex.sprint("\\midrule")
tex.sprint(string.format(
"\\textbf{Total} & & \\textbf{\\$%.2f} \\\\", total_revenue
))
\end{luacode}
\bottomrule
\end{tabular}
\end{document}Conditional Document Sections
\begin{luacode}
local show_appendix = true -- set from external config or passed data
if show_appendix then
tex.sprint("\\appendix")
tex.sprint("\\section{Appendix A: Raw Data}")
tex.sprint("Full dataset available on request.")
end
\end{luacode}Mathematical Computation
\begin{luacode}
-- Compute statistics and embed results
local values = {23, 45, 12, 67, 34, 89, 56, 78, 90, 11}
local sum = 0
for _, v in ipairs(values) do sum = sum + v end
local mean = sum / #values
local sq_sum = 0
for _, v in ipairs(values) do sq_sum = sq_sum + (v - mean)^2 end
local std = math.sqrt(sq_sum / #values)
tex.sprint(string.format(
"Mean: %.2f, Standard deviation: %.2f", mean, std
))
\end{luacode}Font Handling in LuaLaTeX
LuaLaTeX supports fontspec just like XeLaTeX:
\documentclass{article}
\usepackage{fontspec}
\usepackage{luacode}
\setmainfont{TeX Gyre Pagella}
\setsansfont{TeX Gyre Heros}
\begin{document}
Custom fonts work the same as in XeLaTeX.
\end{document}The choice between LuaLaTeX and XeLaTeX for font-heavy documents without Lua scripting comes down to speed: XeLaTeX is typically 20–40% faster than LuaLaTeX because it does not load the Lua interpreter.
API Call via FormaTeX
LATEX=$(cat lualatex-document.tex)
curl -X POST https://api.formatex.io/api/v1/compile \
-H "X-API-Key: $FORMATEX_KEY" \
-H "Content-Type: application/json" \
-d "{\"engine\": \"lualatex\", \"content\": $(echo "$LATEX" | jq -Rs .)}" \
--output output.pdfOr from TypeScript:
const latex = `
\\documentclass{article}
\\usepackage{luacode}
\\begin{document}
\\directlua{
for i = 1, 5 do
tex.sprint("Item " .. i .. ". ")
end
}
\\end{document}
`;
const response = await fetch("https://api.formatex.io/api/v1/compile", {
method: "POST",
headers: {
"X-API-Key": process.env.FORMATEX_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify({ content: latex, engine: "lualatex" }),
});
const pdf = Buffer.from(await response.arrayBuffer());LuaLaTeX vs. XeLaTeX
| Feature | LuaLaTeX | XeLaTeX |
|---|---|---|
| Custom fonts (fontspec) | Yes | Yes |
| Native Unicode | Yes | Yes |
| Lua scripting | Yes | No |
| Speed | Slower (Lua overhead) | Faster |
| TeX internals access | Yes (via Lua) | No |
| Use when | Programmatic content needed | Fonts + Unicode only |
If you only need custom fonts and Unicode support without Lua scripting, use XeLaTeX — it is faster and uses fewer resources per compilation. Reserve LuaLaTeX for documents that actually use \directlua or luacode environments.
Get Started
- Sign up for free — pdflatex free, LuaLaTeX on Pro+
- Pro plan — all four engines, 500 compilations/month
- Engine comparison — full guide to all four engines
\end{article}
\related{posts}




