Drive menu and price-list signage from a Sheet
Generate display slides for a Northwind venue — menus or price lists driven by a Sheet.
Published Dec 7, 2025
Northwind’s reception screens cycle through a menu deck — drinks, snacks, add-ons — and the prices change just often enough to be annoying. When a new SKU lands or the supplier nudges a price up, somebody has to open Slides, find the right text box, and try not to mistype a decimal. The deck drifts out of sync with the till, and the customer notices before anyone else does.
This script flips the source of truth: prices live in a Sheet, the deck is rebuilt from that Sheet on a schedule. Edit a cell, wait for the next run, and every screen in the venue catches up at once. Group the items by category in the Sheet and the deck mirrors the layout — one slide per section, one line per item, prices already in pounds.
What you’ll need
- A Google Sheet (the
Menutab) with headerscategory,name,pricein row 1 and one row per item beneath. The category controls which slide an item lands on, so consistent spelling matters. - A Slides deck to act as the live signage. The script clears every slide on each run, so dedicate a deck to this job rather than reusing a working one.
- Nothing else — the script creates each slide from a built-in layout, so no template fiddling is required.
The script
// The Sheet that holds the menu items, one row per item.
const MENU_SHEET_ID = '1abcMenuId';
// The Slides deck the venue screens display. Rebuilt from scratch each run.
const MENU_DECK_ID = '1abcMenuDeckId';
// Currency symbol shown next to each price. Swap for '$' or '€' if you
// localise the signage.
const CURRENCY = '£';
/**
* Rebuilds the menu deck from the Sheet. One slide per category, one
* line per item, sorted in the order rows appear in the Sheet.
*/
function rebuildMenuSlides() {
// 1. Read every row from the menu Sheet.
const items = readSheet(MENU_SHEET_ID);
if (!items.length) {
Logger.log('No menu items found — leaving the deck untouched.');
return;
}
// 2. Open the deck and wipe the old slides. Doing it after the read
// means a misconfigured Sheet ID can't blank the signage by mistake.
const deck = SlidesApp.openById(MENU_DECK_ID);
for (const slide of deck.getSlides()) slide.remove();
// 3. Bucket items by category, preserving Sheet order within each group.
const byCategory = new Map();
for (const i of items) {
if (!i.category || !i.name) continue; // skip blank rows
if (!byCategory.has(i.category)) byCategory.set(i.category, []);
byCategory.get(i.category).push(i);
}
// 4. Build one slide per category from a title-and-body layout.
for (const [cat, list] of byCategory) {
const slide = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_BODY);
slide.getPlaceholders()[0].asShape().getText().setText(cat);
slide.getPlaceholders()[1].asShape().getText().setText(
list.map((i) => `${i.name} — ${CURRENCY}${i.price}`).join('\n')
);
}
Logger.log(`Rebuilt ${byCategory.size} category slide(s) from ${items.length} item(s).`);
}
/**
* 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
rebuildMenuSlidesreads the menu Sheet withreadSheet, turning each row into an object keyed by the headers in row 1.- If the Sheet is empty, it logs and returns — the script never blanks the signage when there is nothing new to show.
- It opens the deck and removes every existing slide, so each run produces a deck that exactly matches the Sheet.
- It groups items into a
Mapkeyed by category. Rows with a missing category or name are quietly skipped, so a half-typed draft row doesn’t crash the run. - For each category it appends a
TITLE_AND_BODYslide, drops the category name into the title placeholder, and joins the item lines into the body placeholder using two spaces around the em-dash. - It logs how many categories and items made it onto the signage, which is
the quickest way to spot a typo in the
categorycolumn splitting one section into two slides.
Example run
A Menu tab with these rows:
| category | name | price |
|---|---|---|
| Drinks | Flat white | 3.20 |
| Drinks | Earl Grey | 2.80 |
| Snacks | Sourdough toastie | 5.50 |
| Snacks | Almond croissant | 3.10 |
Produces a two-slide deck:
- Slide 1 — Drinks: “Flat white — £3.20” and “Earl Grey — £2.80”.
- Slide 2 — Snacks: “Sourdough toastie — £5.50” and “Almond croissant — £3.10”.
Change 2.80 to 3.00 in the Sheet, run the script, and both screens are
correct before the next customer reaches the till.
Trigger it
Run it by hand the first few times while you tune the layout. Once the deck looks right, install a time-driven trigger so the signage refreshes itself:
- In the Apps Script editor, open Triggers (clock icon).
- Add a trigger for
rebuildMenuSlides, event source Time-driven, running every hour. - Pair it with an installable
onEditif you want edits to land within seconds rather than at the top of the next hour.
Watch out for
- Every run clears the deck. If a manager has nudged a text box around by hand, the next run will undo their work — keep the layout in the slide master, not in one-off edits.
- Prices come through as numbers from the Sheet, so
3.2displays as£3.2, not£3.20. Format thepricecolumn as a number with two decimals, or wrapi.priceinNumber(i.price).toFixed(2)if you want the script to enforce it. - Long category names overflow the title placeholder. Keep them short — Drinks, not Hot drinks and seasonal specials — or switch to a layout with a larger title region.
- Categories appear in the order they first show up in the Sheet. Reorder rows in the Sheet to reorder the slides; there is no separate sort step.
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
Build a Slides-based countdown timer
Embed a live countdown timer in a Northwind deck — refreshed via a scheduled script.
Updated Nov 30, 2025