Generate PDFs from Google Sheets: One PDF per Row
You have a spreadsheet where every row is a person, an order, or a client — and you need every row turned into its own personalized PDF: an invoice, a certificate, a letter. Here are the two free ways to do it (and what each one costs you in time), plus the one-click way.
For every method below, the setup is the same: a Google Sheet where row 1 holds column headers and each following row holds one record. Something like this:
| Name | Course | Completion Date | Instructor |
|---|---|---|---|
| Maya Torres | Data Analytics Fundamentals | Jun 12, 2026 | J. Whitfield |
| Daniel Kim | Data Analytics Fundamentals | Jun 12, 2026 | J. Whitfield |
| Priya Nair | Data Analytics Fundamentals | Jun 12, 2026 | J. Whitfield |
And a Google Docs template with placeholder tags in {{double braces}} that match those headers — {{Name}}, {{Course}}, {{Completion Date}}, {{Instructor}}. The goal: three rows in, three finished PDFs out.
Way 1: The manual export (and why it isn't this)
Google Sheets does have a built-in PDF export: File → Download → PDF (.pdf). But it does something entirely different from what we're after. It exports the sheet itself — one PDF of your spreadsheet grid, rows and columns and gridlines, exactly as they'd print. Three hundred rows in, one PDF of a table out. It has no concept of a template, no way to produce a separate document per row, and no way to lay a row's values into an invoice or certificate design. It's the right tool for sharing a read-only snapshot of a spreadsheet, and the wrong tool for per-row document generation — so if that's what you need, skip straight to one of the next two ways.
Way 2: A free Google Apps Script
Google Sheets ships with a built-in scripting environment, and about 40 lines of JavaScript can do a real per-row merge: copy a Docs template, swap each {{tag}} for the row's value, export the result as a PDF into a Drive folder, and clean up after itself.
What you need first
- A Google Docs template containing
{{tags}}that exactly match your column headers (case-sensitive). Grab its ID from the URL — the long string between/d/and/edit. - A Drive folder for the finished PDFs. Its ID is the string after
/folders/in the folder's URL.
The script
/**
* Generate one PDF per sheet row from a Google Docs template.
* 1. Replace TEMPLATE_ID and FOLDER_ID below.
* 2. Run createPdfs() and authorize when prompted.
*/
var TEMPLATE_ID = 'PASTE_YOUR_DOC_TEMPLATE_ID_HERE';
var FOLDER_ID = 'PASTE_YOUR_DRIVE_FOLDER_ID_HERE';
function createPdfs() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var rows = sheet.getDataRange().getDisplayValues();
var headers = rows.shift(); // row 1 = column headers
var template = DriveApp.getFileById(TEMPLATE_ID);
var folder = DriveApp.getFolderById(FOLDER_ID);
rows.forEach(function (row, i) {
if (row.join('') === '') return; // skip blank rows
// 1. Copy the template into the output folder
var copy = template.makeCopy('merge-row-' + (i + 2), folder);
var doc = DocumentApp.openById(copy.getId());
var body = doc.getBody();
// 2. Swap every {{Header}} tag for this row's value
headers.forEach(function (header, c) {
body.replaceText('\\{\\{' + escapeForRegex(header) + '\\}\\}', row[c]);
});
doc.saveAndClose(); // flush edits before exporting
// 3. Export the filled-in copy as a PDF, named after column A
var pdfBlob = doc.getAs('application/pdf').setName(row[0] + '.pdf');
folder.createFile(pdfBlob);
// 4. Trash the intermediate Doc copy
copy.setTrashed(true);
});
}
// replaceText() treats its search string as a regular expression,
// so headers containing characters like ( ) $ . + ? must be escaped.
function escapeForRegex(text) {
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
A few deliberate choices worth knowing about: getDisplayValues() reads each cell as formatted, so currency and date formatting survive into the PDF; the PDF is named after the first column (change row[0] to use a different one); and every header is regex-escaped before it reaches replaceText(), because that method matches by regular expression — an unescaped header like Amount ($) would silently fail to match.
How to run it
Open the script editor. In your Google Sheet, go to Extensions → Apps Script. A new tab opens with an empty project.
Paste the script over the placeholder myFunction code, then replace TEMPLATE_ID and FOLDER_ID with your real IDs.
Run it. Select createPdfs in the toolbar's function dropdown and click Run. The first run asks you to authorize access to your Sheets, Docs, and Drive — that's the script acting as you. If Google warns the app is unverified (normal for personal scripts), click Advanced, then Go to <project name>.
Check your Drive folder. One PDF per row should be waiting, named after column A.
The honest limits
- The 6-minute wall. Apps Script executions are capped at 6 minutes per run — for consumer Gmail accounts and standard Google Workspace accounts alike. Copying, editing, exporting, and trashing a Doc takes a few seconds per row, so a few hundred rows can hit the ceiling mid-run.
- No status tracking, no resume. If the run dies at row 180, nothing records which rows finished. Re-running starts from row 2 and generates duplicates of everything that already succeeded — unless you add checkpointing code yourself.
- Regex sharp edges.
replaceText()matches by regular expression. The script above escapes your headers for you, but if you modify it — or hardcode your own patterns — special characters in tags will bite. - You're the maintainer. Renamed columns, a moved template, a new teammate who needs to run it — every change lands on whoever owns the script.
Way 3: Batch Merge — the no-code way
Batch Merge is a Google Sheets add-on that does the same per-row merge without the script, and adds the two things the script can't give you: a status cell per row and a log of every run.
Install it free from the Google Workspace Marketplace, then open your sheet and choose Extensions → Batch Merge → Open Batch Merge.
Pick your template — a Google Docs template with {{tag}} placeholders (or a Slides template, on Pro). Batch Merge scans it and shows every tag as a pill: green when it matches a column header, amber when it doesn't — so mismatches are visible before you generate anything, not after.
Choose PDF output and a Drive folder for the finished files.
Click Run. One PDF per row lands in your folder, each row gets a status cell showing what happened to it, and a Merge Log tab records the run — so a re-run after a hiccup doesn't mean guessing what already generated.
The free plan includes 25 merge runs per month — each run can process your whole sheet. Pro ($6.99/month or $59/year) adds per-row email delivery (each PDF sent to the address in that row), scheduled merges, and Google Slides templates.
Don't want to build a template from scratch? Start from the free template library — the basic invoice and the certificate of completion are both merge-ready with tags already in place.
Which way should you use?
| Method | What you get | Setup | Cost | Best for |
|---|---|---|---|---|
| File → Download → PDF | One PDF of the whole spreadsheet grid — not per-row documents | 10 seconds | Free | Sharing a snapshot of the sheet itself |
| Apps Script | One PDF per row; no status tracking or resume | 15–30 min, requires code | Free (6-min cap per run) | Developers and one-off jobs |
| Batch Merge | One PDF per row + per-row status + Merge Log | ~2 minutes, no code | Free: 25 runs/mo · Pro $6.99/mo | Recurring document workflows |
FAQ
Can it email each PDF automatically?
With the Apps Script route, you'd write that yourself (and watch the daily email quota). With Batch Merge, per-row email delivery is a Pro feature: add an email column, and each generated PDF is attached to a personalized email sent to that row's address.
What formats besides PDF can I generate?
Batch Merge can output each row's document as a PDF, a DOCX, or a native Google Doc. Slides templates (Pro) can also output PPTX.
Do my column headers have to match the {{tags}} exactly?
Yes — in both the script and the add-on, matching is exact and case-sensitive: {{Name}} only matches a column headed Name. The difference is when you find out: the script silently leaves unmatched tags in the finished PDFs, while Batch Merge shows an amber pill for every unmatched tag before you run.
How many PDFs can I generate in one go?
The script generates until it finishes or hits the 6-minute execution cap — a few hundred rows is the practical range per run. A Batch Merge run processes your whole sheet, and the free plan includes 25 runs per month.
Related templates
One PDF per row, without the script
Batch Merge is free to install — 25 merge runs per month, no credit card.
Install Batch Merge Free