FormaTeX

\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.

·5 min read·
LuaLaTeX via API: Programmable PDFs with Lua Scripting

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{}:

latex
\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:

latex
\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:

latex
\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

latex
\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

latex
\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:

latex
\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

bash
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.pdf

Or from TypeScript:

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

FeatureLuaLaTeXXeLaTeX
Custom fonts (fontspec)YesYes
Native UnicodeYesYes
Lua scriptingYesNo
SpeedSlower (Lua overhead)Faster
TeX internals accessYes (via Lua)No
Use whenProgrammatic content neededFonts + 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

\end{article}

Back to blog

\related{posts}

One quick thing

We track anonymous usage — page views, feature usage, compilation events — to understand what works and what doesn't. No ads, no personal data, no third-party sharing.

Cookie policy