appscript.dev
Automation Advanced Slides

Build a template-enforcement tool

Reset stray slides back to the Northwind master layout — fix decks that drifted.

Published Sep 28, 2025

Every Northwind deck starts on-brand and ends up off-brand. Someone duplicates a slide, deletes the title placeholder, drops a free-floating text box where the header should be, and now the layout system has nothing to grab onto. A month later the deck looks like a ransom note. The fix is not “lecture the designers” — it is to scan for slides that have lost their placeholders and reset them back to the master layout.

This script walks a deck and replaces any slide that has no placeholders left with a fresh slide on the default layout. The original is removed, the replacement keeps its position in the deck order, and the rest of the deck is left alone. It is deliberately conservative: it only touches slides that have nothing left to anchor to, so a stylistic flourish is safe but a fully freeformed slide gets reset.

What you’ll need

  • A Google Slides deck that has drifted from its template.
  • A copy of the deck to test on — this script is destructive and removes slides. Always run it on a copy first.
  • Nothing else — no external services, no API keys.

The script

// The layout to reset stray slides to. Change this to whatever your master
// uses as its default body layout — TITLE_AND_BODY is the most common.
const DEFAULT_LAYOUT = SlidesApp.PredefinedLayout.TITLE_AND_BODY;

/**
 * Replaces any slide that has lost all its placeholders with a fresh slide
 * on the given layout. The replacement is inserted at the same position so
 * the deck order is preserved.
 *
 * @param {string} deckId  The Slides file ID.
 * @param {SlidesApp.PredefinedLayout} [layout] Layout to use for resets.
 * @returns {number} How many slides were reset.
 */
function enforceLayout(deckId, layout = DEFAULT_LAYOUT) {
  if (!deckId) throw new Error('enforceLayout needs a deckId.');

  const deck = SlidesApp.openById(deckId);
  const slides = deck.getSlides();
  let reset = 0;

  // 1. Iterate over a snapshot of the slides so removing one mid-loop does
  //    not throw off the index of the others.
  slides.forEach((slide, index) => {
    // 2. A slide with no placeholders has nothing the master layout can
    //    bind to — that's the signature of a freeformed slide.
    if (slide.getPlaceholders().length > 0) return;

    // 3. Insert the replacement at the same index, then remove the
    //    original. Doing it in this order keeps the deck order stable.
    deck.insertSlide(index, layout);
    slide.remove();
    reset++;

    Logger.log(`Reset slide ${index + 1} to ${layout}.`);
  });

  Logger.log(`Done — reset ${reset} slide(s) in deck ${deckId}.`);
  return reset;
}

/**
 * Convenience wrapper so you can click Run in the editor without writing a
 * caller. Replace the ID with your own — and run it on a COPY first.
 */
function enforceNorthwindDeck() {
  enforceLayout('1abcDeckCopyId');
}

How it works

  1. enforceLayout opens the deck and snapshots the slides into an array. By iterating over the snapshot rather than re-reading deck.getSlides() on every step, removing a slide mid-loop does not throw off the indexes of the remaining ones.
  2. For each slide it asks getPlaceholders() — these are the title, body, and other shapes the master layout owns. A slide with at least one placeholder is still connected to the template, so the script leaves it alone.
  3. When a slide returns zero placeholders, it has been freeformed past the point of recovery. The script inserts a fresh slide on the default layout at the same index, then removes the offending slide. Doing the insert first preserves the deck’s slide order.
  4. It logs the slide number it reset so the designer can go back and re-create the content on a clean canvas.
  5. At the end it returns the count, useful if you want to wire this into a bigger report or alert on decks that drifted badly.

Example run

Say 1abcDeckCopyId is a 10-slide deck where slides 3 and 7 have had every placeholder deleted and replaced with floating shapes. After a run:

SlideBeforeAfter
1Title slide (intact)Unchanged
2Body slide (intact)Unchanged
3Freeformed — no placeholdersReset to TITLE_AND_BODY
4-6Body slides (intact)Unchanged
7Freeformed — no placeholdersReset to TITLE_AND_BODY
8-10Body slides (intact)Unchanged

The log shows:

Reset slide 3 to TITLE_AND_BODY.
Reset slide 7 to TITLE_AND_BODY.
Done — reset 2 slide(s) in deck 1abcDeckCopyId.

The designer now has two blank-but-on-brand slides at the right positions and the content to rebuild from the previous version.

Run it

This is an on-demand job — run it when a deck audit turns up drift:

  1. Make a copy of the original deck. This script is destructive.
  2. In the Apps Script editor, select enforceNorthwindDeck and click Run.
  3. Approve the authorisation prompt the first time.
  4. Open the copy, check which slides were reset, and rebuild their content on the fresh placeholders.

For a stricter version, expand the rule: flag slides whose placeholders are present but empty, or whose title text doesn’t match the master’s typography. Those checks belong in a separate “audit” function that reports rather than mutates.

Watch out for

  • This is destructive. The original slide is removed, including any content the script could not preserve. Always run on a copy and compare side by side before replacing the live deck.
  • It does not migrate content. A reset slide is blank — the script makes no attempt to find the title text or bullets on the freeformed version and copy them over. That’s intentional: guessing wrong silently loses content.
  • The placeholder-count heuristic is conservative. A slide with one placeholder left is considered “fine”, even if the rest of it is a mess. Tighten the check if your template defines a specific minimum.
  • Layouts must exist on the master. PredefinedLayout.TITLE_AND_BODY works on any standard Slides theme, but a custom theme may have renamed or removed it — pick a layout that actually exists in your deck’s master.

Related