The problem with "generate PDFs at scale"
Most teams start with a one-at-a-time approach: loop through rows in a database, call a PDF generation function for each row, save the file. This works for ten invoices. It does not work for ten thousand. The bottleneck is usually one of three things: the rendering engine is too slow (if you're using Puppeteer or wkhtmltopdf), the loop isn't parallelized so you wait for each PDF before generating the next, or there is no error handling so one bad row kills the whole run.
Typsetter's batch endpoint solves all three problems. Rendering is handled by Typst (a compiled engine written in Rust), parallelization happens on our infrastructure, and individual row errors are captured without stopping the batch. Let's build it.
Prerequisites
- A Typsetter account — start free, no credit card required
- An invoice template in your account (use the invoice-professional starter from the store)
- Your API key from the dashboard under Settings → API Keys
- Node.js 18+ installed locally (curl examples included for every step)
Step 1: Prepare your CSV file
The batch endpoint accepts a CSV where each row becomes one PDF. Column headers must match the variable names in your template exactly. For the invoice-professional template, the expected variables are:
| CSV Column | Template Variable | Example Value |
|---|---|---|
| client_name | {{ client_name }} | Acme Corporation |
| client_email | {{ client_email }} | billing@acme.com |
| invoice_number | {{ invoice_number }} | INV-2026-00001 |
| invoice_date | {{ invoice_date }} | 2026-02-01 |
| due_date | {{ due_date }} | 2026-03-01 |
| service_description | {{ service_description }} | Web Development |
| amount | {{ amount }} | 4500.00 |
| currency | {{ currency }} | USD |
Here is a minimal example CSV:
Typsetter auto-detects CSV delimiters (comma, semicolon, tab) and character encodings (UTF-8, UTF-8 BOM, Latin-1). You do not need to pre-process your export from Excel or Google Sheets.
Step 2: Upload the batch job via API
The batch endpoint is a multipart form upload. You send the CSV file, the template slug, and optional configuration parameters. The response contains a job_id you use to poll for status.
The filename_column parameter tells Typsetter which CSV column to use as the PDF filename inside the ZIP. Using invoice_number means each PDF will be named INV-2026-00001.pdf, INV-2026-00002.pdf, and so on.
Step 3: Poll for job status (or use the webhook)
For large batches, rendering is asynchronous. Poll the status endpoint or configure a webhook URL (shown above) to be notified on completion.
Step 4: Full Node.js implementation
Here is a complete Node.js script that submits the batch, polls for completion with exponential backoff, and downloads the ZIP when finished.
Run npm install form-data node-fetch before executing this script. On Node.js 21+, you can use the built-in fetch and FormData globals and skip those imports.
Step 5: Set up a webhook for completion notification
If your batch takes 3–5 minutes, you don't want to poll — you want to be notified. Pass a webhook_url when submitting the batch, and Typsetter will POST a signed JSON payload to your endpoint when the job finishes.
Tips for large batches
Keep templates simple for speed
Templates that embed large images or use complex Typst scripting will render slower. For batch use, strip unnecessary decorations. A clean invoice template with a logo URL (fetched once, cached) will render in 200–300ms per page.
Use the filename_column parameter
Without it, PDFs are named sequentially (1.pdf, 2.pdf). With filename_column=invoice_number, each file is named by its invoice number, making the ZIP directly usable without renaming.
Handle failed rows
When a batch completes, the progress.failed count tells you how many rows errored. Download the error report from GET /v1/batch/{job_id}/errors to get a CSV of failed rows with error messages. Fix the data and resubmit those rows as a smaller batch.
Plan limits
Batch processing is available on all paid plans. The Growth plan supports batches up to 1,000 rows. Pro and above supports up to 10,000 rows. If you need more, contact us for enterprise limits.
What 10,000 PDFs looks like in practice
In testing on the Pro plan infrastructure, a batch of 10,000 single-page invoices using the invoice-professional template takes approximately 4 minutes and 20 seconds end-to-end. The resulting ZIP is around 180MB (individual PDFs average 18KB each). That works out to roughly 38 PDFs rendered per second across the parallel processing pool.
For comparison, generating the same 10,000 invoices sequentially with Puppeteer at 4.2 seconds each would take 11.6 hours. Even with a 10-worker parallel setup, you would wait over an hour. The architecture matters.
The batch endpoint counts against your plan's monthly PDF quota. A batch of 10,000 rows consumes 10,000 PDF credits. Check your usage on the dashboard before submitting large batches to avoid surprises.
Ready to generate your first batch?
Start with 100 free PDFs per month. No credit card required. Upgrade when you need more.