FormaTeX

\begin{article}

LaTeX Certificate Generation at Scale via API

Batch-generate pixel-perfect PDF certificates using the FormaTeX API. Template design, bulk generation patterns, rate limit handling, and plan selection.

·5 min read·
LaTeX Certificate Generation at Scale via API

Certificates of completion, diplomas, and achievement awards need to look premium — they are documents people frame, share, and keep. LaTeX produces pixel-perfect PDFs with precise typography, consistent spacing, and professional quality that no HTML-to-PDF tool can match. This guide covers the full pipeline: template design, data injection, batch generation, and scaling.

Why Certificates Need Pixel-Perfect PDFs

A certificate is a credentialing document. It represents something — a completed course, a passed exam, an earned achievement. Blurry fonts, misaligned text, or inconsistent spacing undermine the perceived value of the credential.

LaTeX advantages for certificates:

  • Vector text — scales perfectly from screen to print at any size
  • Precise positioning — exact coordinate placement on the page
  • Font control — any OpenType font with proper kerning and ligatures
  • Consistent output — same result every time, regardless of server environment
  • No browser rendering quirks — no HTML-to-PDF layout surprises

Template Design

A professional certificate uses the geometry package for precise margins and tikz for decorative elements:

latex
\documentclass[12pt]{article}
\usepackage[
  paperwidth=29.7cm,
  paperheight=21cm,
  margin=2cm
]{geometry}
\usepackage{fontspec}         % xelatex — for custom fonts
\usepackage{tikz}
\usepackage{xcolor}

\setmainfont{EB Garamond}
\definecolor{gold}{RGB}{212,175,55}
\definecolor{navy}{RGB}{31,41,90}

\pagestyle{empty}

\begin{document}

\begin{tikzpicture}[remember picture, overlay]
  % Border frame
  \draw[line width=3pt, gold]
    ([shift={(1cm,1cm)}]current page.south west)
    rectangle
    ([shift={(-1cm,-1cm)}]current page.north east);

  % Inner border
  \draw[line width=1pt, gold]
    ([shift={(1.3cm,1.3cm)}]current page.south west)
    rectangle
    ([shift={(-1.3cm,-1.3cm)}]current page.north east);
\end{tikzpicture}

\begin{center}
  \vspace*{2cm}

  {\fontsize{14}{17}\selectfont\color{navy}\textsc{Certificate of Completion}}

  \vspace{1.5cm}

  {\fontsize{12}{14}\selectfont This certifies that}

  \vspace{1cm}

  {\fontsize{28}{32}\selectfont\color{navy}\textit{Jane Smith}}

  \vspace{0.8cm}

  {\fontsize{12}{14}\selectfont has successfully completed}

  \vspace{0.5cm}

  {\fontsize{18}{22}\selectfont\textbf{Advanced LaTeX Document Automation}}

  \vspace{0.5cm}

  {\fontsize{11}{13}\selectfont on February 24, 2026}
\end{center}

\end{document}

Parameterized Template Function

For batch generation, build a template function that accepts recipient data:

typescript
interface CertificateData {
  recipientName: string;
  courseName: string;
  completionDate: string;
  certificateId: string;
  instructorName?: string;
}

function buildCertificateLatex(data: CertificateData): string {
  const escape = (s: string) =>
    s
      .replace(/&/g, "\\&")
      .replace(/%/g, "\\%")
      .replace(/\$/g, "\\$")
      .replace(/#/g, "\\#")
      .replace(/_/g, "\\_");

  return `
\\documentclass[12pt]{article}
\\usepackage[paperwidth=29.7cm,paperheight=21cm,margin=2cm]{geometry}
\\usepackage{fontspec}
\\usepackage{tikz}
\\usepackage{xcolor}

\\setmainfont{EB Garamond}
\\definecolor{gold}{RGB}{212,175,55}
\\definecolor{navy}{RGB}{31,41,90}
\\pagestyle{empty}

\\begin{document}
\\begin{center}
  \\vspace*{2cm}
  {\\fontsize{14}{17}\\selectfont\\color{navy}\\textsc{Certificate of Completion}}
  \\vspace{1.5cm}

  {\\fontsize{28}{32}\\selectfont\\color{navy}\\textit{${escape(data.recipientName)}}}
  \\vspace{0.8cm}

  {\\fontsize{18}{22}\\selectfont\\textbf{${escape(data.courseName)}}}
  \\vspace{0.5cm}

  {\\fontsize{11}{13}\\selectfont on ${escape(data.completionDate)}}
  \\vspace{2cm}

  {\\tiny Certificate ID: ${escape(data.certificateId)}}
\\end{center}
\\end{document}
`;
}

Batch Generation

For bulk certificate generation, process recipients with controlled concurrency to stay within rate limits:

typescript
async function generateCertificate(data: CertificateData): Promise<Buffer> {
  const latex = buildCertificateLatex(data);

  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: "xelatex" }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.log ?? error.error);
  }

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

// Process in batches to respect rate limits
async function generateBatch(
  recipients: CertificateData[],
  concurrency: number = 5
): Promise<Map<string, Buffer>> {
  const results = new Map<string, Buffer>();
  const errors: { id: string; error: string }[] = [];

  for (let i = 0; i < recipients.length; i += concurrency) {
    const batch = recipients.slice(i, i + concurrency);

    const batchResults = await Promise.allSettled(
      batch.map(async (recipient) => {
        const pdf = await generateCertificate(recipient);
        return { id: recipient.certificateId, pdf };
      })
    );

    for (const result of batchResults) {
      if (result.status === "fulfilled") {
        results.set(result.value.id, result.value.pdf);
      } else {
        errors.push({ id: "unknown", error: result.reason.message });
      }
    }

    // Brief pause between batches
    if (i + concurrency < recipients.length) {
      await new Promise((resolve) => setTimeout(resolve, 200));
    }
  }

  if (errors.length > 0) {
    console.error(`${errors.length} certificates failed:`, errors);
  }

  return results;
}

Do not fire all requests simultaneously for large batches. Use a concurrency limit (5–10 parallel requests) to avoid hitting rate limits and to get consistent response times. The batch function above processes 5 at a time with a 200ms pause between batches.

Rate Limits at Scale

PlanMonthly compilationsBatch size for 1,000 certificates
Free15Not practical for batch generation
Pro5001 batch of 500 per month
Max2,0002 batches per month
Enterprise15,00015 full batches per month

For certificate generation at scale (thousands per month), the Max or Enterprise plan is appropriate.

Plan Selection

Choose your plan based on expected monthly certificate volume:

  • Free: Up to 15 certificates/month — proof of concept only
  • Pro: Up to 500/month — small online courses, internal certification
  • Max: Up to 2,000/month — growing e-learning platform
  • Enterprise: Up to 15,000/month — large certification programs, enterprise training

Use xelatex for certificates — it supports custom fonts (Inter, Garamond, Playfair Display) via fontspec, which dramatically improves certificate quality over pdflatex's limited font support. This requires Pro plan or above.

Async Compilation and Webhooks for Batch Notification

For large batches, use async compilation instead of synchronous requests. Submit all certificates as async jobs (POST /compile/async), then either poll for results or set up a webhook to receive notifications when each certificate is ready. This is more resilient for large batches — failed jobs can be retried individually without blocking the rest.

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