When you actually need bulk generation
You have a list of attendees from an event signup or LMS export, and each one needs a personalized PDF certificate with their name, the event, the date, and ideally a unique verification code. Two common approaches show up in most stacks.
Two paths
A dedicated certificate platform (Certifier, Templated, IssueBadge) wraps a designer UI around CSV import and email delivery. Best when the design changes often and non-developers own the workflow.
An HTML to PDF API is what you want when the certificate template should live in source control, you already have a job runner, or you want to embed a unique URL or QR per recipient. You write one HTML and CSS template, loop your attendee list, and render one PDF per call.
A working Node example
Anvil's PDF Generation API takes HTML and CSS and returns the PDF bytes. Install the client and call it in a loop:
npm install @anvilco/anvilimport fs from 'fs'
import path from 'path'
import Anvil from '@anvilco/anvil'
const anvil = new Anvil({ apiKey: process.env.ANVIL_API_KEY })
const attendees = [
{ name: 'Ada Lovelace', event: 'AnalyticEngine 2026', date: '2026-05-12', certId: 'CERT-0001' },
{ name: 'Alan Turing', event: 'AnalyticEngine 2026', date: '2026-05-12', certId: 'CERT-0002' },
// ... up to thousands more
]
const css = `
body { font-family: Helvetica, sans-serif; text-align: center; padding: 80px; }
h1 { font-size: 36px; margin-bottom: 8px; }
.name { font-size: 48px; font-weight: bold; margin: 32px 0; }
.meta { font-size: 14px; color: #555; }
`
for (const a of attendees) {
const html = `
<h1>Certificate of Completion</h1>
<p>This certifies that</p>
<div class="name">${a.name}</div>
<p>completed <strong>${a.event}</strong> on ${a.date}.</p>
<p class="meta">Certificate ID: ${a.certId}</p>
`
const { statusCode, data } = await anvil.generatePDF({
title: `Certificate ${a.certId}`,
type: 'html',
data: { html, css },
})
if (statusCode !== 200) continue
fs.writeFileSync(path.join('out', `${a.certId}.pdf`), data, { encoding: null })
}Each call returns the raw PDF bytes. Write the file with no encoding or the output will be corrupt.
What to plan for at scale
Rate limits. Free plan API access is throttled; the Product Pack raises production rate limits to 40 requests per second. 1,000 certificates at that ceiling finish in under a minute.
Cost. Generation is metered at $0.10 per PDF, with 2,500 free credits per month once metered usage is enabled on your account. Bulk pre-purchase starts at 25,000 credits for a lower per-unit rate.
Delivery and verification. The API only returns bytes. Pair the loop with S3 or your CDN for storage, an email step (SendGrid, Postmark, SES) for delivery, and a public lookup page like /verify/CERT-0001 if you want scan-to-verify with a QR code rendered into the HTML template.
Back to All Questions