appscript.dev
Automation Intermediate Docs Slides

Convert a Doc outline into a Slides deck

Turn Doc headings and bullets into a Slides deck — one slide per H1.

Published Oct 26, 2025

Northwind’s strategy deck starts life as a Doc outline. Writing the argument in prose is faster than fighting slide placeholders, so the thinking happens in a Doc — and then someone spends an afternoon copying headings onto title slides and bullets into body boxes by hand. The deck and the outline drift apart the moment that copying starts.

This script does the copying for you. It reads a Doc where each Heading 1 marks a slide and the paragraphs beneath it are that slide’s content, then builds a Slides deck with one slide per H1. Rework the outline in the Doc, run it again, and you get a fresh deck — the Doc stays the single source of truth.

What you’ll need

  • A Google Doc structured as an outline: every section starts with a Heading 1, and the lines under it are the body content for that slide.
  • A title for the new deck. The script creates the deck itself, so you only supply the name.
  • Nothing else — Slides is created fresh on each run.

The script

// The Doc that holds the outline — the long string from its URL.
const OUTLINE_DOC_ID = '1abcDocId';

// The title for the deck the script creates.
const DECK_TITLE = 'Northwind Strategy';

/**
 * Reads an outline Doc and builds a Slides deck: one slide per Heading 1,
 * with the paragraphs beneath each heading as that slide's body text.
 * Returns the URL of the new deck.
 */
function docToDeck() {
  const body = DocumentApp.openById(OUTLINE_DOC_ID).getBody();

  // 1. Create the deck and set its built-in title slide.
  const deck = SlidesApp.create(DECK_TITLE);
  const intro = deck.getSlides()[0];
  intro.getPlaceholders()[0].asShape().getText().setText(DECK_TITLE);

  // 2. Walk the Doc top to bottom, building slides as we go.
  let currentSlide = null;
  for (let i = 0; i < body.getNumChildren(); i++) {
    const c = body.getChild(i);

    // Skip anything that is not a paragraph (tables, images, page breaks).
    if (c.getType() !== DocumentApp.ElementType.PARAGRAPH) continue;
    const para = c.asParagraph();
    const text = para.getText();

    // Skip blank lines so they do not pad the body text.
    if (!text) continue;

    if (para.getHeading() === DocumentApp.ParagraphHeading.HEADING1) {
      // 3. A Heading 1 starts a new slide — set its title, clear its body.
      currentSlide = deck.appendSlide(SlidesApp.PredefinedLayout.TITLE_AND_BODY);
      currentSlide.getPlaceholders()[0].asShape().getText().setText(text);
      currentSlide.getPlaceholders()[1].asShape().getText().setText('');
    } else if (currentSlide) {
      // 4. Any other line becomes a body line on the current slide.
      const bodyShape = currentSlide.getPlaceholders()[1].asShape().getText();
      bodyShape.appendText(text + '\n');
    }
  }

  Logger.log('Built deck: ' + deck.getUrl());
  return deck.getUrl();
}

How it works

  1. docToDeck opens the outline Doc and creates a new Slides deck with the configured title.
  2. It sets the deck’s first slide — the built-in title slide — to DECK_TITLE by writing into placeholder 0.
  3. It loops through every child element of the Doc body in order. Anything that is not a paragraph (a table, an image, a page break) is skipped, as are blank paragraphs.
  4. When it hits a Heading 1, it appends a new TITLE_AND_BODY slide, writes the heading text into the title placeholder, and clears the body placeholder so it starts empty.
  5. Any other non-blank paragraph is appended to the current slide’s body placeholder, one line at a time. Lines before the first Heading 1 are ignored, because there is no slide to attach them to yet.
  6. It logs and returns the deck URL so you can open it straight away.

Example run

Say the outline Doc reads:

Market Position          (Heading 1)
We lead in the mid-market.
Enterprise share is growing.

Risks                    (Heading 1)
Two competitors cut prices.
Supplier costs are up 8%.

After a run you get a three-slide deck:

SlideTitleBody
1Northwind Strategy(title slide)
2Market PositionWe lead in the mid-market. / Enterprise share is growing.
3RisksTwo competitors cut prices. / Supplier costs are up 8%.

Each Heading 1 became one slide, and the lines beneath it became that slide’s body text — no manual copying.

Run it

This is an on-demand job — run it whenever the outline is ready to present:

  1. In the Apps Script editor, set OUTLINE_DOC_ID and DECK_TITLE, select docToDeck, and click Run.
  2. Approve the authorisation prompt the first time.
  3. Check the execution log for the deck URL, or open it from your Drive.

Watch out for

  • It creates a new deck every run. Run it three times and you have three decks in your Drive. If you want to refresh one deck in place, open a fixed deck by ID and clear its slides instead of calling SlidesApp.create.
  • Content before the first Heading 1 is dropped. Lines that appear above the first H1 have no slide to land on, so they are silently skipped — keep your intro text under a heading.
  • Long sections overflow the slide. The body placeholder does not auto-shrink past a point; if a section has fifteen lines, Slides will spill them off the edge. Split dense sections into more Heading 1s.
  • It copies plain text only. Bold, links, and nested bullet levels from the Doc are flattened — every body line lands as a plain bullet. Restyle in Slides afterwards if presentation matters.

Related