Merge several team decks into one
Combine Northwind teams' contributions into a single all-hands deck in a defined order.
Published Oct 12, 2025
The Northwind all-hands runs every quarter and the deck is a stitched-together beast. Design has six slides on the new brand work, Engineering has eight on the platform migration, Ops has four on the new offices. Everyone builds their own section in their own deck, and the night before the meeting someone has the job of copy-pasting all of them into a master file in the right order — losing fonts, swapping animations, and finding a missing slide at 11pm.
This script does the merge in a single pass. You hand it a list of deck IDs
in presentation order, it creates a fresh master deck and appends every slide
from every source. master.appendSlide(slide) preserves the source slide’s
content and layout, so the merged deck looks like the originals — no font
re-mapping, no broken images. The night-before scramble becomes a one-click
job.
What you’ll need
- Two or more Google Slides decks to merge. They should be readable by the account running the script.
- A naming convention for the output. The example timestamps the title so successive runs don’t overwrite each other.
- Nothing else — the script creates the master deck itself.
The script
// Source decks in presentation order. The first deck supplies the master
// theme; later decks bring their own layouts along.
const TEAM_DECKS = [
'1abcDesignTeamDeckId',
'1abcDevTeamDeckId',
'1abcOpsTeamDeckId',
];
/**
* Creates a fresh master deck and appends every slide from each source deck
* in order. Returns the URL of the new deck.
*
* @param {string[]} deckIds Source deck IDs, in the order to merge.
* @param {string} outputTitle Title for the new master deck.
* @returns {string} The URL of the merged deck.
*/
function mergeDecks(deckIds, outputTitle) {
if (!Array.isArray(deckIds) || !deckIds.length) {
throw new Error('mergeDecks needs at least one source deck ID.');
}
if (!outputTitle) {
throw new Error('mergeDecks needs an outputTitle.');
}
// 1. Create the master and drop the auto-generated blank first slide.
const master = SlidesApp.create(outputTitle);
master.getSlides()[0].remove();
let merged = 0;
for (const id of deckIds) {
let src;
try {
src = SlidesApp.openById(id);
} catch (e) {
// A missing or permission-denied deck shouldn't throw away the whole
// merge — log it and keep going with the rest.
Logger.log(`Could not open deck ${id}: ${e.message}`);
continue;
}
// 2. appendSlide(slide) copies the slide into the master, preserving
// its layout, content, and (mostly) its formatting.
for (const slide of src.getSlides()) {
master.appendSlide(slide);
merged++;
}
Logger.log(`Appended ${src.getSlides().length} slides from "${src.getName()}".`);
}
// 3. Save once at the end so all the appends are written together.
master.saveAndClose();
Logger.log(`Merged ${merged} slides into "${outputTitle}".`);
return master.getUrl();
}
/**
* Convenience wrapper for the quarterly all-hands. Generates a dated title
* so each run creates a new master deck without clobbering the previous one.
*/
function buildAllHands() {
const date = new Date().toISOString().slice(0, 10);
const url = mergeDecks(TEAM_DECKS, `All-hands — ${date}`);
Logger.log(`Open the merged deck: ${url}`);
}
How it works
mergeDecksvalidates its inputs first — a missing array or empty title would otherwise crash insideSlidesAppwith a less helpful error.- It creates a fresh master deck with
SlidesApp.create(), which adds an empty title slide by default. The script removes that slide so the merge starts with a clean file. - It iterates the source deck IDs in order. Each
openByIdis wrapped in a try/catch — a missing or permission-denied deck should not blow away the merge of the other teams. - For each source deck it walks the slides and calls
master.appendSlide(slide). That call copies the slide into the master, preserving its layout, placeholders, text, images, and shapes. The merged deck looks like the originals stitched together. - After every deck it logs a per-deck count, so when you skim the log you can confirm Design contributed six, Engineering eight, and so on.
- It calls
saveAndClose()once at the end. Slides batches edits in memory until save, so doing this in one go is faster than letting Apps Script flush after every append. buildAllHandsis the practical entry point — it timestamps the title so you can run it weekly without overwriting last week’s master.
Example run
Say the three team decks have these slide counts:
| Source deck | Slides |
|---|---|
| Design — Q3 update | 6 |
| Engineering — platform migration | 8 |
| Ops — new offices | 4 |
After running buildAllHands on 12 October 2025, a new file appears in your
Drive root named All-hands — 2025-10-12. It contains 18 slides in this
order: Design first, then Engineering, then Ops — each section preserving the
original layouts and theme. The log prints the URL so you can open it
straight from the editor.
Run it
This is an on-demand job — you run it once per all-hands:
- In the Apps Script editor, select
buildAllHandsand click Run. - Approve the authorisation prompt the first time.
- Open the URL from the log and skim the merged deck.
For a recurring meeting, pair this with a time-based trigger that runs every Friday morning, then drop the link into a Slack channel — see the swap-logos recipe for an example of walking a Drive folder.
Watch out for
- Themes don’t always merge cleanly. The master inherits its theme from the
blank deck
SlidesApp.create()produces. If your teams use different master layouts, some slides may pick up new defaults. Either standardise on a template before merging, or create the master from a template copy. - Linked content is fragile. Charts linked to Sheets, embedded videos, and external image references usually survive but occasionally come across as static — spot-check the merged deck before sharing.
- Order matters and is silent. If you reorder the IDs in
TEAM_DECKS, the merge order changes without warning. Keep the array close to the script with a comment per line so it stays maintainable. - The master ends up in your Drive root. If your team has a folder
convention, move it after creation with
DriveApp.getFileById(...).moveTo(folder).
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