Automation · 3 min read
By Murtaza Aziz
Google Sheets to PDF: Turn Each Row into Its Own Document
Export a sheet as CSV, map columns to template fields, run preflight, download a zip of PDFs. Works for invoices, certificates, and statements. CSV batch starts on Starter, 100 rows per run.
Google Sheets is where a lot of billing lists actually live: a tab of students, a tab of monthly invoices, a tab of event tickets. You do not need an engineering team to turn each row into a PDF. You need a template, a CSV export, and a column map.
Docuplate's CSV to PDF flow is that job. It is not "print the sheet as a PDF", which gives you one ugly landscape of cells. It is one finished document per row, using the same template you already designed for a single download.
Shape the sheet so columns match fields
Put a header row on row 1. Use names you can map with a straight face: customer_name, invoice_number, amount, due_date. Nested JSON (customer.name) can wait; the mapper accepts column headers and writes them onto template paths.
Line items are the awkward part. A flat sheet has one row per document. If each invoice has several lines, either flatten (description_1, qty_1, description_2, qty_2) and map those into an items array in the batch UI, or keep one row per line and generate packing slips instead of invoices. For month-end invoices with a single total, one row is enough.
Export CSV, not the Google PDF printer
File → Download → Comma-separated values. UTF-8. If you use commas inside descriptions, Sheets already quotes those fields. Open the file in a text editor once if a column looks concatenated; a stray semicolon as the delimiter is the usual cause outside the US.
Upload that CSV in Docuplate on Starter ($19/mo) or above. Batch PDF generation maps each header to a template path, runs preflight per row, and stops you from downloading 80 broken files because column E was blank.
Preflight, then download the zip
Preflight is the reason to use this instead of a mail merge that fails on row 41. Empty required paths, bad dates, missing items arrays show up before the zip. Fix the sheet, re-export, upload again. The template does not change.
Each batch is capped at 100 rows. A 240-row statement run is three uploads. Annoying, but cheaper than writing a script for a quarterly job. If this is daily and the sheet is only a cache of a database, move to the REST API and skip CSV.
Name the downloaded zip after the run ("invoices-2026-08.csv" in the sheet title is a decent start) so you can tell last month's batch from this one when a customer asks which file they were in.
Sheets as a trigger, not only as a file
If a new row should produce a PDF immediately, export-and-upload is the wrong shape. Use Make or Zapier on "new row in Google Sheets" and POST that row to the generate endpoint (Pro). Or skip Sheets: a Docuplate public form writes the payload without a spreadsheet in the middle.
Use CSV when the list already exists and you want the documents this afternoon. Use a webhook or API when the row will keep arriving.
Dates are the usual surprise. Sheets stores 45900 internally and displays 1 Sep 2026. CSV download can emit either, depending on locale. If preflight rejects due_date, format the column as YYYY-MM-DD before export (Format → Number → Custom date). Currency columns should be plain numbers, not "$1,200.00" text, if your template expects a number for totals.