Generate a deck from each row of a Sheet
Build one Slides deck per record automatically — Northwind case-study decks, generated from the Projects sheet.
Published Jun 22, 2025
Northwind’s sales team keeps a tidy Case studies Sheet — one row per
project, with the client name, the brief, and the outcome. What they really
want, before a pitch, is a one-deck case study they can attach to an email:
same layout, same fonts, just the right project’s details swapped in. Today
that means duplicating a template, find-and-replacing three fields, and
re-naming the file. For fifty projects, it eats an afternoon.
This script takes the same template, walks the Sheet, and produces one deck per row — each saved into a dedicated folder, named after the client and project. Run it once and the whole library appears. Add a new row tomorrow and re-run it; the new row gets its own deck and the old ones are untouched (as long as they already exist).
What you’ll need
- A Slides template containing the layout for one case study. Anywhere the
client, project, or outcome should appear, write
{{client}},{{project}}, and{{outcome}}. - A
Case studiesSheet with headersclient,project,outcomein row 1 and one row per project beneath. - A Drive folder where the generated decks should be saved. Keep it separate from the template so a future “delete everything in the output folder” tidy-up never touches the master.
The script
// The Slides template — the layout every generated deck is cloned from.
const TEMPLATE = '1abcCaseStudyTemplateId';
// The Sheet that lists every project to turn into a deck.
const PROJECTS = '1abcCaseStudiesSheetId';
// The Drive folder where generated decks land.
const DECKS_FOLDER = '1abcDecksFolderId';
/**
* Reads the projects Sheet and produces one deck per row, copied from
* the template and saved into the output folder.
*/
function generateDecksPerRow() {
// 1. Read the Sheet. Row 1 holds the headers used to look up columns.
const [h, ...rows] = SpreadsheetApp.openById(PROJECTS).getSheets()[0]
.getDataRange().getValues();
const col = Object.fromEntries(h.map((k, i) => [k, i]));
if (!rows.length) {
Logger.log('No projects in the Sheet — nothing to generate.');
return;
}
const folder = DriveApp.getFolderById(DECKS_FOLDER);
// 2. For each row, copy the template into the output folder and replace
// every placeholder across every slide. The name combines client and
// project so listings sort sensibly.
let made = 0;
for (const r of rows) {
const client = r[col.client];
const project = r[col.project];
if (!client || !project) continue; // skip half-empty rows
const copy = DriveApp.getFileById(TEMPLATE)
.makeCopy(`${client} — ${project}`, folder);
const deck = SlidesApp.openById(copy.getId());
for (const slide of deck.getSlides()) {
slide.replaceAllText('{{client}}', client);
slide.replaceAllText('{{project}}', project);
slide.replaceAllText('{{outcome}}', r[col.outcome] || '');
}
made++;
}
Logger.log(`Generated ${made} deck(s) into ${folder.getName()}.`);
}
How it works
generateDecksPerRowreads the entireCase studiesSheet in onegetDataRange().getValues()call — far faster than reading row by row when the Sheet grows.- It builds a
collookup mapping header name to column index. That means reordering Sheet columns or inserting a new one between existing columns does not require a code change. - If the Sheet has no project rows, it logs and exits without touching Drive.
- It opens the output folder once, outside the loop, so the script does not re-resolve the folder for every project.
- For each row, it skips records that are missing the client or project
name — those are usually half-typed drafts — and otherwise copies the
template into the output folder with a meaningful name like
Acme — Brand refresh. - It iterates every slide in the new deck and runs
replaceAllTextfor each placeholder. Running across all slides means the template can have multiple pages of layout and they all get filled in. - It logs a count so you can confirm the run produced the expected number of decks.
Example run
A Case studies Sheet with these rows:
| client | project | outcome |
|---|---|---|
| Acme Ltd | Brand refresh | Conversions up 38% |
| Boreal Bank | Mobile redesign | App-store rating 3.2 → 4.6 |
| Carve Coffee | Loyalty programme | Repeat orders up 22% |
Produces three decks in the output folder:
- Acme Ltd — Brand refresh
- Boreal Bank — Mobile redesign
- Carve Coffee — Loyalty programme
Each deck is a copy of the template with the placeholders swapped for that row’s values. Open the Acme one and the outcome slide reads “Conversions up 38%” instead of “{{outcome}}”.
Run it
This is an on-demand job, usually triggered after a quarterly review when several new projects have been added to the Sheet:
- In the Apps Script editor, select
generateDecksPerRowand click Run. - Approve the authorisation prompt the first time.
- Open the output folder in Drive to find the new decks, named after each project.
Watch out for
- Re-running the script makes a fresh copy every time, even for projects
already in the folder. The originals are not deleted, so you end up with
duplicates named
Acme — Brand refreshandAcme — Brand refresh (1). Add a check that callsfolder.getFilesByName(name).hasNext()before copying, or write a deck URL back to the Sheet and skip rows that already have one (see Create personalized pitch decks at scale). - The script reads the Sheet in one go, which is fast, but
makeCopyandopenByIdare network calls — fifty rows means a hundred round trips and a couple of minutes of execution time. For very large catalogues expect to hit the six-minute execution limit and split the work into batches. replaceAllTextis case-sensitive and exact. Keep your placeholders in lowercase and avoid extra spaces inside the braces —{{ client }}will not match{{client}}.- Special characters in client or project names appear verbatim in the Drive filename. Slashes and colons get sanitised by Drive, but very long names can be awkward to scan in a folder listing — keep names tight.
Related
Extract all deck text into a sheet
Pull text out of every slide for review, translation, or copy-editing.
Updated Jan 4, 2026
Generate sales-enablement decks per segment
Tailor Northwind's messaging slides by audience segment — fintech, healthcare, retail.
Updated Dec 28, 2025
Insert chapter divider slides from an outline
Add section-break slides between chapters in a Northwind deck.
Updated Dec 21, 2025
Build a deck accessibility checker
Flag missing alt text, low contrast, and tiny fonts across a Northwind deck.
Updated Dec 14, 2025
Drive menu and price-list signage from a Sheet
Generate display slides for a Northwind venue — menus or price lists driven by a Sheet.
Updated Dec 7, 2025