appscript.dev
Automation Beginner Slides

Insert chapter divider slides from an outline

Add section-break slides between chapters in a Northwind deck.

Published Dec 21, 2025

Long Northwind decks — the annual review, the all-hands, the internal training sequence — all benefit from chapter dividers. A title-only slide between sections gives the audience a moment to catch up, signals a topic change, and saves the presenter from awkwardly saying “right, moving on”. The trouble is that decks shift as they are written, and inserting dividers by hand means recalculating which slide number each one belongs at every time.

This script takes an outline — a list of { beforeSlide, title } objects in the order they appear — and inserts a title-layout divider slide before each target slide. The clever bit is the running offset: each insertion pushes later slides down by one, so the script keeps a counter and adjusts the index as it goes. You write the outline once, and a deck that already has 6 chapter breaks becomes one with 6 fresh divider slides in the right places.

What you’ll need

  • A Google Slides deck to add dividers to.
  • An outline of chapters: each item gives the slide the divider should appear before (1-based, against the original deck order) and the title to show on the divider.
  • Nothing else — no external services or API keys.

The script

// The layout to use for divider slides. TITLE is the simplest — one big
// centred heading. Switch to SECTION_HEADER if your template defines it.
const DIVIDER_LAYOUT = SlidesApp.PredefinedLayout.TITLE;

/**
 * Inserts a divider slide before each entry in `chapters`. The chapter list
 * is in original-deck coordinates — the script applies a running offset so
 * later inserts land where you expect.
 *
 * @param {string} deckId    The Slides file ID.
 * @param {Array<{beforeSlide: number, title: string}>} chapters
 *        Chapter breaks in deck order (1-based slide numbers).
 */
function insertDividers(deckId, chapters) {
  if (!deckId) throw new Error('insertDividers needs a deckId.');
  if (!Array.isArray(chapters) || !chapters.length) {
    throw new Error('insertDividers needs at least one chapter.');
  }

  const deck = SlidesApp.openById(deckId);
  let inserted = 0;

  // 1. Sort by target position so the running-offset trick works. If the
  //    caller already passed them in order this is a no-op.
  const ordered = [...chapters].sort((a, b) => a.beforeSlide - b.beforeSlide);

  for (const ch of ordered) {
    if (!ch.title) {
      Logger.log(`Skipping chapter at slide ${ch.beforeSlide} — no title.`);
      continue;
    }

    // 2. Each previous insert pushed later slides down by one, so add the
    //    running offset to convert the original index to the live index.
    //    insertSlide is 0-based, but beforeSlide is 1-based.
    const liveIndex = (ch.beforeSlide - 1) + inserted;

    const divider = deck.insertSlide(liveIndex, DIVIDER_LAYOUT);

    // 3. Set the title on the first placeholder. TITLE layout has exactly
    //    one — if you switch to SECTION_HEADER, this still works.
    const placeholders = divider.getPlaceholders();
    if (placeholders.length) {
      placeholders[0].asShape().getText().setText(ch.title);
    } else {
      Logger.log(`Inserted divider at ${ch.beforeSlide} but layout had no placeholder.`);
    }

    inserted++;
  }

  Logger.log(`Inserted ${inserted} divider slides into deck ${deckId}.`);
}

/**
 * Convenience wrapper with a sample outline. Replace the deck ID and
 * chapters with your own.
 */
function run() {
  insertDividers('1abcDeckId', [
    { beforeSlide: 1, title: 'Overview' },
    { beforeSlide: 6, title: 'Case studies' },
    { beforeSlide: 12, title: 'Next steps' },
  ]);
}

How it works

  1. insertDividers validates its inputs first — running the script with an empty array would silently do nothing and look like a bug.
  2. It sorts the chapters by beforeSlide before iterating. The running-offset technique only works when later inserts come after earlier ones, so the sort makes the function robust to callers that pass chapters out of order.
  3. For each chapter it skips entries without a title — a blank divider is never what you want.
  4. The index math is the heart of the script. beforeSlide is in original-deck coordinates (1-based), but every previous insert has pushed later slides down by one. Subtracting one (for 0-based) and adding inserted (the running offset) gives the right index for insertSlide.
  5. deck.insertSlide(index, layout) adds a new slide on the given layout at that position and returns the new slide. The TITLE layout has a single title placeholder, which the script fills in with the chapter title.
  6. It logs the total at the end so you can confirm the count matches the outline before opening the deck.

Example run

Say 1abcDeckId is a 14-slide deck and the outline is:

beforeSlidetitle
1Overview
6Case studies
12Next steps

After a run, the deck has grown to 17 slides:

PositionSlide
1Overview (new divider)
2Original slide 1
6Case studies (new divider)
7Original slide 6
14Next steps (new divider)
15Original slide 12
16-17Original slides 13-14

The log reads Inserted 3 divider slides into deck 1abcDeckId.

Run it

This is an on-demand job — run it whenever the outline is final:

  1. Write your outline in the run wrapper or pass it from another script.
  2. In the Apps Script editor, select run and click Run.
  3. Approve the authorisation prompt the first time.
  4. Open the deck and check that the dividers landed in the expected places.

For a recurring deck (a weekly all-hands, say), keep the outline in a spreadsheet and have the script read it — see the populate-speaker-notes recipe for the sheet-reading pattern.

Watch out for

  • The chapter indexes are in original-deck coordinates. If you re-run the script on a deck that already has dividers, every new divider is inserted in addition to the old ones — there is no idempotency check.
  • The TITLE layout has exactly one placeholder. If you swap it for a custom layout with no title placeholder, the script logs a warning but still inserts the slide, leaving the title blank.
  • Slide numbering in the outline must match what humans see in the deck. Hidden or skipped slides still count — Slides numbers by position, not by visibility.
  • The script does not style the divider. It inherits the master layout’s typography. If you want bigger text or a brand colour, adjust the master layout itself rather than per-divider in code.

Related