FormaTeX

\begin{article}

draft — not published

Multi-File LaTeX Documents via API

Compile LaTeX projects with includes, bibliography files, and custom classes via the FormatEx API — no zip required, just structured JSON payloads.

·8 min read·
Multi-File LaTeX Documents via API

Most LaTeX tutorials treat a document as a single .tex file. Real projects are not. An academic paper has a bibliography. A thesis has chapter files split across directories. A journal submission requires a custom .cls file you cannot strip out. If you have ever tried to compile a multi-file LaTeX project through a naive API, you know the result: File not found errors, missing references, and an incomplete PDF. FormatEx handles multi-file LaTeX API compilation natively — you send every file your document needs as a structured JSON payload, and the API compiles them together exactly as latexmk would on your local machine.

This tutorial walks through the complete workflow: structuring your payload, handling \input and \include, attaching bibliography files, bundling custom classes, and picking the right engine for each use case.

How Multi-File Compilation Works in FormatEx

The FormatEx compile endpoint accepts a files array alongside your main document content. Each entry in the array describes one file: its path relative to the compilation root and its content as a UTF-8 string. The API writes all files to an ephemeral workspace, then runs your chosen engine against the main entry point.

text
POST https://api.formatex.io/api/v1/compile
X-API-Key: fex_your_key_here
Content-Type: application/json

The request body shape for a multi-file job:

json
{
  "engine": "latexmk",
  "main": "main.tex",
  "files": [
    {
      "path": "main.tex",
      "content": "..."
    },
    {
      "path": "sections/introduction.tex",
      "content": "..."
    },
    {
      "path": "references.bib",
      "content": "..."
    },
    {
      "path": "custom.cls",
      "content": "..."
    }
  ]
}

The main field tells the API which file is the entry point. Everything else in files is available to that document via its relative path. Subdirectories are created automatically — you do not need to declare them separately.

Structuring an Academic Paper Payload

Consider a realistic conference paper layout:

text
main.tex
abstract.tex
sections/
  introduction.tex
  related-work.tex
  methodology.tex
  results.tex
  conclusion.tex
figures/
  architecture.pdf
acm-style.cls
references.bib

Here is how you send this to the FormatEx API in TypeScript using the native fetch API:

typescript
import fs from "fs";

const API_URL = "https://api.formatex.io/api/v1/compile";
const API_KEY = process.env.FORMATEX_API_KEY!;

function readFile(filePath: string): { path: string; content: string } {
  return {
    path: filePath,
    content: fs.readFileSync(filePath, "utf-8"),
  };
}

async function compileAcademicPaper(): Promise<Buffer> {
  const files = [
    readFile("main.tex"),
    readFile("abstract.tex"),
    readFile("sections/introduction.tex"),
    readFile("sections/related-work.tex"),
    readFile("sections/methodology.tex"),
    readFile("sections/results.tex"),
    readFile("sections/conclusion.tex"),
    readFile("acm-style.cls"),
    readFile("references.bib"),
    // Binary assets like PDFs must be base64-encoded
    {
      path: "figures/architecture.pdf",
      content: fs.readFileSync("figures/architecture.pdf").toString("base64"),
      encoding: "base64",
    },
  ];

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
    },
    body: JSON.stringify({
      engine: "latexmk",
      main: "main.tex",
      files,
    }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Compilation failed: ${error.error}`);
  }

  return Buffer.from(await response.arrayBuffer());
}

compileAcademicPaper()
  .then((pdf) => fs.writeFileSync("output.pdf", pdf))
  .then(() => console.log("PDF written to output.pdf"))
  .catch(console.error);

The main.tex entry point uses standard \input and \include commands exactly as you would locally:

latex
\documentclass{acm-style}

\usepackage[backend=biber,style=acmnumeric]{biblatex}
\addbibresource{references.bib}

\begin{document}
\maketitle

\input{abstract}
\include{sections/introduction}
\include{sections/related-work}
\include{sections/methodology}
\include{sections/results}
\include{sections/conclusion}

\printbibliography
\end{document}

No changes to your LaTeX source are needed. The API resolves paths relative to the compilation root, so \include{sections/introduction} maps directly to the file at sections/introduction.tex in your payload. For a deeper look at automating this kind of workflow, see automating academic paper PDFs with the LaTeX API.

Choosing the Right Engine for Bibliography Passes

Bibliography compilation is where single-pass engines fail. If you use \bibliography with BibTeX or \addbibresource with BibLaTeX, you need multiple compiler passes to resolve citations and generate the reference list.

EngineBibliography supportPassesUse case
pdflatexManual (requires bibtex/biber separately)1Simple documents, no bibliography
xelatexSame as pdflatex1Unicode fonts, no bibliography
lualatexSame as pdflatex1Lua scripting, complex fonts
latexmkAutomatic (runs biber/bibtex as needed)2—4Academic papers, bibliographies, cross-references

For any document using \cite, \ref, \label, or a bibliography, use latexmk. It detects what auxiliary tools are needed, runs them in the correct order, and re-runs the main compiler until all references are resolved. Sending a BibTeX document to pdflatex through the API will produce a PDF with [?] placeholders instead of citations. For a full breakdown of pdfLaTeX vs XeLaTeX vs LuaLaTeX trade-offs, the engine guide covers each engine's strengths in detail.

The engine choice is a single field change in your request body:

json
{
  "engine": "latexmk",
  "main": "main.tex",
  "files": [...]
}

Using Custom Document Classes and Style Files

Many journals and conferences distribute their own .cls or .sty files and require submissions to use them. The FormatEx API treats these exactly like any other file — include them in the files array at the path your \documentclass or \usepackage command expects.

A common pattern is to collect all supplementary class files into a directory and reference them with a directory walker:

python
import os
import requests

def load_files_from_directory(directory: str) -> list[dict]:
    files = []
    for root, _, filenames in os.walk(directory):
        for filename in filenames:
            full_path = os.path.join(root, filename)
            rel_path = os.path.relpath(full_path, directory)
            rel_path = rel_path.replace("\\", "/")
            with open(full_path, "r", encoding="utf-8") as f:
                files.append({"path": rel_path, "content": f.read()})
    return files

def compile_with_custom_class(project_dir: str) -> bytes:
    files = load_files_from_directory(project_dir)

    response = requests.post(
        "https://api.formatex.io/api/v1/compile",
        headers={
            "X-API-Key": os.environ["FORMATEX_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "engine": "latexmk",
            "main": "main.tex",
            "files": files,
        },
        timeout=120,
    )

    response.raise_for_status()
    return response.content

pdf_bytes = compile_with_custom_class("./my-paper")
with open("paper.pdf", "wb") as f:
    f.write(pdf_bytes)

This walks your entire project directory and sends every file to the API, preserving the relative path structure. It works for any depth of nesting. The same pattern applies when building reusable LaTeX API document templates — you structure shared class files once and reference them across all your projects.

Debugging Failed Multi-File Compilations

When a multi-file compilation fails, the API returns the full LaTeX log in the error response. Common failure patterns and their causes:

  • File 'sections/introduction.tex' not found — the file path in your files array does not match what \include expects. Check for trailing slashes, mismatched capitalization, or missing .tex extension.
  • Citation 'smith2023' undefined — you used pdflatex instead of latexmk, or your .bib file is missing from the files array.
  • \documentclass{acmart} not found — the .cls file is missing or its path in the files array does not match the \documentclass argument.
  • Package biblatex Error: File 'references.bib' not found — the path passed to \addbibresource does not match the path in your files array exactly.

For a complete reference of error codes and what each status means, see the LaTeX API error codes guide.

To inspect the log programmatically:

typescript
const response = await fetch(API_URL, { method: "POST", headers, body });

if (!response.ok) {
  const error = await response.json();
  // error.log contains the full LaTeX compiler output
  console.error("Compilation log:\n", error.log);
  throw new Error(error.error);
}

Reading the log directly is faster than guessing. The LaTeX error messages are verbose but precise.

Plan Limits for Multi-File Projects

Multi-file compilation does not cost more per job — one API call is one compilation regardless of how many files are in the payload. What matters for plan selection is total payload size and the engine you need:

  • The Free plan supports only pdflatex and caps payload size at 1 MB. Adequate for simple documents, but not for bibliography workflows or custom classes.
  • The Developer plan at $12/month unlocks all four engines including latexmk, which is required for bibliography compilation.
  • The Pro plan at $49/month raises the size limit to 10 MB, covering most academic projects including embedded figures.
  • The Scale plan at $199/month supports up to 25 MB payloads, suitable for books, dissertations, and large reports.

Start Compiling Multi-File Documents Today

Multi-file LaTeX API compilation is available on all paid FormatEx plans. You send the files, FormatEx handles the TeX Live infrastructure, and you get a PDF back — no Docker containers, no TeX installation, no build scripts to maintain. If you are evaluating whether to self-host TeX Live vs. use a compilation API, the infrastructure savings become especially clear when you factor in multi-pass bibliography compilation across many projects.

Sign up at formatex.io to get your API key. The free tier lets you test single-file documents immediately; upgrade to Developer or higher when you are ready to compile full academic papers with bibliography passes and custom document classes.

\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