// Slides · Sheets · Apps Script

Generate certificates from a template in Google Slides.

Use Apps Script to copy a Slides template once per row, replace placeholder text, then call saveAndClose() before exporting — skipping that flush is why your PDFs come out blank.

I need to auto-generate personalized certificates from a Google Slides template without getting blank PDFs.

The script

copy · paste · trigger
generateCertificates.gs
Apps Script
// generateCertificates.gs
// Sheet cols: A=name, B=course, C=date. Row 1 = header.
function generateCertificates() {
  var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Recipients');
  var rows = sheet.getDataRange().getValues();
  var templateId = 'PASTE_TEMPLATE_SLIDE_ID_HERE';
  var folderId = 'PASTE_OUTPUT_FOLDER_ID_HERE';
  var folder = DriveApp.getFolderById(folderId);

  for (var i = 1; i < rows.length; i++) {
    var name = rows[i][0];
    var course = rows[i][1];
    var date = rows[i][2];
    var copy = DriveApp.getFileById(templateId).makeCopy(name + ' Certificate', folder);
    var pres = SlidesApp.openById(copy.getId());
    var slide = pres.getSlides()[0];
    slide.replaceAllText('{{NAME}}', name);
    slide.replaceAllText('{{COURSE}}', course);
    slide.replaceAllText('{{DATE}}', date);
    pres.saveAndClose();
    var pdf = copy.getAs('application/pdf');
    folder.createFile(pdf).setName(name + ' Certificate.pdf');
  }
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

Copy first, then open — not the other way

The template stays intact because you call DriveApp.getFileById(templateId).makeCopy() before touching SlidesApp at all. The copy lands in your output folder and gets a new file ID. Only then do you open it with SlidesApp.openById(). Opening the original and editing it in place is the mistake that corrupts your master slide.

replaceAllText() is a presentation-level method on a Slide object, not on the Presentation. Call it on each slide you need to modify. For a single-slide certificate, pres.getSlides()[0] is the whole job. If your template spans multiple slides, loop over pres.getSlides() and call replaceAllText on each.

The flush call that actually matters

SlidesApp queues edits in memory. When you call getAs('application/pdf') on the Drive File object — not on the Presentation object — Drive reads the file as it exists on disk, not as it exists in your script's in-memory Presentation instance. That is why people get PDFs of the blank template: the writes never hit storage before the export.

The fix is one line: pres.saveAndClose() before you touch the copy via DriveApp. This forces Apps Script to flush the queued text replacements to Drive. I keep a comment above that line in every certificate script I write, because the first time I hit this it took an embarrassingly long time to figure out why every PDF looked identical.

After saveAndClose(), the Presentation object is closed and unusable. Any further SlidesApp calls on it will throw. That is fine — you have everything you need already on disk.

Staying inside the 6-minute execution limit

Apps Script enforces a hard 6-minute wall per execution (30 minutes for Workspace accounts). For batches up to roughly 50 rows with a single-slide template, a plain for loop is fine. Beyond that, write the last-processed row index back to a named range after each iteration and trigger the function again from a continuation trigger — Apps Script's ContinuationToken approach, or just a time-based trigger that reads the resume index.

PDF creation is the slow step, roughly 1-2 seconds per file. If you only need the Slides copy and not a PDF, drop the getAs and createFile lines entirely. The Slides files in the folder are already individually named and shareable.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why do my exported PDFs show the original template text instead of the replaced values?
You are exporting before Apps Script flushes its queued edits to Drive. Add pres.saveAndClose() immediately after your replaceAllText calls and before any getAs or email call. Without it, the file on disk is still the untouched copy.
Can I use replaceAllText on shapes, tables, and text boxes, or only on plain text frames?
replaceAllText works on all text-containing elements in the slide: text boxes, shapes, table cells, and speaker notes. It does a literal string match, so your placeholder must match exactly including case and curly braces.
How do I email each certificate as a PDF attachment in the same script?
After pres.saveAndClose(), get the blob with copy.getAs('application/pdf'), then call MailApp.sendEmail(recipientEmail, subject, body, {attachments: [pdf]}). The recipient email would come from a column in your sheet, same as name and course.
Does makeCopy preserve custom fonts and background images from the template?
Yes. makeCopy is a Drive-level file copy — it duplicates the file byte-for-byte, including embedded fonts, images, master slides, and layouts. Anything visible in the original will appear in the copy.