appscript.dev
Automation Intermediate Slides Drive

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

  1. mergeDecks validates its inputs first — a missing array or empty title would otherwise crash inside SlidesApp with a less helpful error.
  2. 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.
  3. It iterates the source deck IDs in order. Each openById is wrapped in a try/catch — a missing or permission-denied deck should not blow away the merge of the other teams.
  4. 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.
  5. 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.
  6. 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.
  7. buildAllHands is 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 deckSlides
Design — Q3 update6
Engineering — platform migration8
Ops — new offices4

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:

  1. In the Apps Script editor, select buildAllHands and click Run.
  2. Approve the authorisation prompt the first time.
  3. 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