Generate product one-pagers from a catalog
Build a slide per SKU with specs and price for every item in Northwind's catalogue.
Published Aug 24, 2025
Northwind’s catalogue lives in a Sheet — one row per SKU, with the name, spec line, price, and a link to the product photo. The sales team keeps asking for a one-pager per product: same layout, same fonts, just the right product’s details and image. Building them by hand means duplicating a template a hundred times, swapping the text three times per slide, and dragging the image into roughly the same place every time. It does not get done.
This script does the duplicating. It reads the catalogue, clones the one-pager template once per SKU within a single deck, and stamps the name, spec, and price onto each slide. If there’s an image URL, it fetches the image and drops it on the slide at a fixed position. The result is a single deck — one slide per product — exported to PDF or shared straight from Drive.
What you’ll need
- A Slides template with a single slide containing the one-pager layout.
Anywhere the name, spec, or price should appear, write
{{name}},{{spec}}, and{{price}}. Leave a clear rectangle where the product image should land — the script inserts the image at a fixed coordinate. - A
CatalogueSheet with headerssku,name,spec,price, andimageUrlin row 1, one row per product. TheimageUrlis optional; rows without one get a slide with no image. - A Drive home for the generated deck. The script saves it in your
default Drive folder under the name
Catalogue one-pagers.
The script
// The one-slide Slides template that defines the one-pager layout.
const TEMPLATE = '1abcOnePagerTemplateId';
// The Sheet holding the catalogue, one row per SKU.
const CATALOGUE = '1abcCatalogueId';
// Currency symbol used in front of the price field.
const CURRENCY = '£';
// Image placement on each slide, in points.
const IMAGE_LEFT = 400;
const IMAGE_TOP = 100;
const IMAGE_WIDTH = 240;
const IMAGE_HEIGHT = 240;
/**
* Reads the catalogue and produces one slide per product, cloned from
* the template into a single deck.
*/
function buildOnePagers() {
// 1. Read the catalogue. Skip the run if there is nothing to build.
const items = readSheet(CATALOGUE);
if (!items.length) {
Logger.log('Catalogue is empty — nothing to generate.');
return;
}
// 2. Copy the template into a fresh deck so the master is never edited.
const master = DriveApp.getFileById(TEMPLATE).makeCopy('Catalogue one-pagers');
const deck = SlidesApp.openById(master.getId());
const templateSlide = deck.getSlides()[0];
// 3. Reuse the existing template slide for the first item, then
// duplicate for each subsequent product.
for (let i = 0; i < items.length; i++) {
const slide = i === 0 ? templateSlide : templateSlide.duplicate();
slide.replaceAllText('{{name}}', items[i].name || '');
slide.replaceAllText('{{spec}}', items[i].spec || '');
slide.replaceAllText('{{price}}', `${CURRENCY}${items[i].price ?? ''}`);
// 4. Fetch and insert the product image, if one is configured.
// A bad URL on one row should never abort the whole run.
if (items[i].imageUrl) {
try {
const blob = UrlFetchApp.fetch(items[i].imageUrl).getBlob();
slide.insertImage(blob, IMAGE_LEFT, IMAGE_TOP, IMAGE_WIDTH, IMAGE_HEIGHT);
} catch (err) {
Logger.log(`Skipped image for ${items[i].name}: ${err.message}`);
}
}
}
Logger.log(`Generated ${items.length} one-pager(s): ${master.getUrl()}`);
}
/**
* Reads a single-tab Sheet into an array of objects, using row 1 as keys.
*/
function readSheet(id) {
const [h, ...rows] = SpreadsheetApp.openById(id).getSheets()[0]
.getDataRange().getValues();
return rows.map((r) => Object.fromEntries(h.map((k, i) => [k, r[i]])));
}
How it works
buildOnePagersreads the catalogue Sheet viareadSheet, which turns each row into an object keyed by the headers in row 1. Empty catalogues short-circuit before any Drive work happens.- It copies the template into Drive under a fixed name, then opens the copy in Slides. The master template is never modified, so a botched run can simply be deleted.
- Slide 0 of the copy is the template layout. The first product fills
that slide directly; every subsequent product calls
templateSlide.duplicate()to clone it. replaceAllTextswaps each{{placeholder}}for the product’s value. Nullish-coalescing (??) and|| ''keep blank cells from rendering as the literalundefinedornull.- The price is prefixed with the configured currency symbol so the slide
reads “£12.99” rather than just “12.99”. Swap
CURRENCYonce at the top of the file if you localise the deck. - If the row has an
imageUrl,UrlFetchApp.fetchpulls the image as a blob andinsertImagedrops it at the configured rectangle. Thetry/catchmeans one broken link only affects one slide; the rest of the run still completes. - It logs the URL of the finished deck so you can jump straight to it from the execution log.
Example run
A Catalogue Sheet with these rows:
| sku | name | spec | price | imageUrl |
|---|---|---|---|---|
| NW-101 | Maple desk | 1400 × 700 mm, oak | 349.00 | https://example.com/desk.jpg |
| NW-102 | Linen chair | Steel frame, washable | 129.00 | https://example.com/chair.jpg |
| NW-103 | Floor lamp | 1.6 m, brushed brass | 89.00 |
Produces a three-slide deck called Catalogue one-pagers:
- Slide 1 — Maple desk, “1400 × 700 mm, oak”, £349.00, with the desk photo on the right.
- Slide 2 — Linen chair, “Steel frame, washable”, £129.00, with the chair photo.
- Slide 3 — Floor lamp, “1.6 m, brushed brass”, £89.00, no image
(the row had no
imageUrl).
Run it
This is an on-demand job — run it when the catalogue Sheet has changed enough to warrant a fresh PDF:
- In the Apps Script editor, select
buildOnePagersand click Run. - Approve the authorisation prompt the first time.
UrlFetchAppneeds external-request access, in addition to the standard Slides, Sheets, and Drive scopes. - Open the new deck from the execution log, then File → Download → PDF to share with the sales team.
Watch out for
- Every run creates a fresh
Catalogue one-pagersfile in your default Drive folder. After a few iterations you will seeCatalogue one-pagers (1),(2), and so on. Either delete the previous run first or look the existing file up by name and update it in place. UrlFetchApp.fetchonly retrieves publicly accessible URLs. Drive share links need to be set to “Anyone with the link” and image hosts must allow direct fetches. Broken links are caught and logged, but the resulting slide will have an empty rectangle where the photo should be.- Prices come straight from the Sheet, so
129displays as£129, not£129.00. Format the price column to two decimals in the Sheet, or wrap the value inNumber(items[i].price).toFixed(2)if you want the script to enforce the trailing zero. - A large catalogue stresses both
UrlFetchAppquotas and the six-minute execution limit. For more than a couple of hundred SKUs, batch the work by reading a slice of the Sheet on each run and tracking progress in a helper column.
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