Guide

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:

NameCourseCompletion DateInstructor
Maya TorresData Analytics FundamentalsJun 12, 2026J. Whitfield
Daniel KimData Analytics FundamentalsJun 12, 2026J. Whitfield
Priya NairData Analytics FundamentalsJun 12, 2026J. 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

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

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?

MethodWhat you getSetupCostBest for
File → Download → PDFOne PDF of the whole spreadsheet grid — not per-row documents10 secondsFreeSharing a snapshot of the sheet itself
Apps ScriptOne PDF per row; no status tracking or resume15–30 min, requires codeFree (6-min cap per run)Developers and one-off jobs
Batch MergeOne PDF per row + per-row status + Merge Log~2 minutes, no codeFree: 25 runs/mo · Pro $6.99/moRecurring 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