Skip to main content
← Blog ··7 min read

How to automate Google Slides with Apps Script: practical patterns

Three patterns to automate Google Slides with Apps Script: text replacement, slide duplication, image swap. With the limits and when to graduate.

A RevOps lead I worked with had inherited a Google Slides deck used for weekly customer health updates. Forty slides. One per major account. The previous owner had been hand-editing eight numbers per slide every Monday morning. Forty slides times eight numbers times “Monday morning” equals a real bill.

She wanted to automate Google Slides without buying anything, without learning Python, and without giving IT a project. Apps Script is exactly the right answer for this shape of problem — small enough that the platform cost would be over-engineering, repetitive enough that manual is genuinely painful. This piece is the working-code version of how to do it, and the honest version of where it breaks.

The longer architectural treatment lives on the Google Slides automation. This piece is the practical tutorial: three patterns that cover most of what teams actually need to automate Google Slides, with code, and a clear-eyed read on the limits.

What Apps Script gives you

Apps Script is Google’s serverless JS environment, scoped to Workspace APIs. For Slides, the relevant entry points are SlidesApp (the high-level wrapper) and Slides.Presentations (the lower-level Advanced Service that exposes more of the underlying API). For most automation work the high-level wrapper is enough.

You attach a script to a deck, write a function, and either run it manually, on a trigger, from a Sheet menu, or from another Workspace surface. The deployment story is friction-free, which is part of why Apps Script wins for small jobs.

Three patterns cover roughly 80% of what teams want to automate. We’ll walk through each.

Pattern 1: Text replacement

The simplest and most-used pattern. Your template has placeholder strings like {{customer_name}}, {{mrr}}, {{health_score}}. The script walks the deck and substitutes each placeholder with a value from a Sheet, an Airtable export, or an API response.

function replaceTextInDeck(deckId, replacements) {
  const presentation = SlidesApp.openById(deckId);

  Object.entries(replacements).forEach(([placeholder, value]) => {
    presentation.replaceAllText(`{{${placeholder}}}`, String(value));
  });

  return presentation.getUrl();
}

function generateWeeklyDeck() {
  const templateId = 'YOUR_TEMPLATE_DECK_ID';
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Accounts');
  const data = sheet.getDataRange().getValues();
  const headers = data[0];

  data.slice(1).forEach((row) => {
    const replacements = Object.fromEntries(
      headers.map((header, i) => [header, row[i]])
    );

    // Duplicate template for each account
    const newDeck = DriveApp.getFileById(templateId)
      .makeCopy(`Weekly health — ${replacements.customer_name}`);

    replaceTextInDeck(newDeck.getId(), replacements);
  });
}

A few things this handles correctly. The replaceAllText call walks every shape in every slide, including text inside grouped shapes and inside speaker notes — usually what you want, sometimes a problem. The replacement is whole-string, not regex, so {{mrr}} and {{mrr_growth}} are independent placeholders.

Things to watch. If a placeholder is split across runs because a designer changed font mid-string, replaceAllText will silently fail to match. The fix is template hygiene — ask the designer to keep placeholders in a single run, ideally in a single style. The other gotcha: replacing with an empty string deletes the run but not the surrounding shape, which can leave odd blank lines.

Pattern 2: Slide duplication from a master

The second pattern is needed any time the deck has a variable number of sections. One slide per customer, one slide per region, one slide per project. The script holds a master slide and duplicates it for each row of input data.

function buildAccountSection(deckId, accounts) {
  const presentation = SlidesApp.openById(deckId);
  const slides = presentation.getSlides();

  // Find the master slide by a known marker
  const masterIndex = slides.findIndex((slide) =>
    slide.getNotesPage().getSpeakerNotesShape()
      .getText().asString().includes('MARKER:account_master')
  );

  if (masterIndex === -1) {
    throw new Error('Master slide not found — add MARKER:account_master to speaker notes');
  }

  const master = slides[masterIndex];

  accounts.forEach((account, i) => {
    const newSlide = master.duplicate();
    // Move it just after the master so order is deterministic
    newSlide.move(masterIndex + 1 + i);

    Object.entries(account).forEach(([key, value]) => {
      newSlide.replaceAllText(`{{${key}}}`, String(value));
    });
  });

  // Remove the master once all duplicates exist
  master.remove();
}

The pattern that matters here is the marker-based master detection. Hard-coding slide indices breaks the moment the designer reorders slides. Storing a marker in the speaker notes — or a hidden text box, or the slide’s object ID if you control it — keeps the script robust to template edits.

The move() call is the bit most tutorials skip. duplicate() puts the new slide immediately after the source, so when you duplicate in a loop the slides end up in reverse order. Explicit move() keeps things sane.

Removing the master at the end is a stylistic choice. Some teams keep the master in place and hide it from the published version, which is safer if the script fails partway through.

Pattern 3: Image replacement

The third pattern shows up whenever reports include screenshots, charts-as-images, or per-record visual assets. The Slides API supports replacing all instances of a placeholder shape with an image from a URL.

function replaceShapesWithImages(deckId, imageMap) {
  const presentation = SlidesApp.openById(deckId);

  Object.entries(imageMap).forEach(([placeholder, imageUrl]) => {
    presentation.replaceAllShapesWithImage(`{{${placeholder}}}`, imageUrl);
  });
}

A few production considerations.

The image URL has to be publicly accessible at the moment the script runs. Google’s servers fetch the image and embed it; private Drive URLs, signed S3 URLs that have expired, or behind-VPN assets all fail silently or with cryptic errors. The pattern that works in production is uploading the image to a public-read storage location with a deterministic URL, then passing that URL.

The placeholder shape needs to be a regular shape with the placeholder text inside it — not a text box, not a table cell. Designers who don’t know this will make the placeholders look right and the script silently fail. Document the convention.

The replacement preserves the placeholder shape’s position and size, scaling the image to fit. This is usually what you want and occasionally infuriating — a portrait image into a landscape placeholder gets cropped. If precise framing matters, pre-process the image to the placeholder’s aspect ratio before passing the URL.

Where Apps Script breaks down

Three failure modes catch most teams.

The six-minute execution timeout. Consumer Apps Script kills any execution that runs longer than six minutes (thirty for Workspace). Generating fifty decks in a tight loop hits this fast — each makeCopy plus replaceAllText is a non-trivial round trip. The workaround is chunking: write a state-aware function that processes ten records, stores progress in a property, and uses time-driven triggers to resume. Workable, but it’s now a small distributed system.

Quota limits. SlidesApp has daily call quotas that aren’t documented to the digit. Heavy bulk-replacement scripts hit them. The errors are obscure (“Service invoked too many times for one day”), the throttling unpredictable. For weekly volume under a hundred decks you’ll never see them; for daily generation across hundreds of decks you’ll meet them within a month.

Chart and data manipulation. Apps Script can update Sheets-linked charts (refresh after Sheet update). It can’t manipulate chart data inside the deck if the chart was pasted as an image, which most designer-built decks do for visual control. If your reports rely on charts that update with new data, you’re either rebuilding charts in Sheets and re-linking, or moving to a tool that handles the binding.

Template fragility. The biggest hidden cost. Apps Script-driven decks are sensitive to designer edits — a font change splits a run, a regrouping moves a shape, a layout swap breaks a placeholder. The script silently produces wrong-looking decks until somebody notices. Production-grade automation needs validation, snapshotting, and someone who owns the template-script pact. That’s a real role.

For a deeper look at the lower-level option, see the Google Slides API practical guide. For the data-side companion — pushing data into a deck from a structured source — see Airtable to Google Slides.

When to graduate

The right time to leave Apps Script is when more of your time goes to fighting Apps Script than to building the report. Concrete signs:

You’re hitting timeouts and writing chunked-resume logic. You’re hand-tracking quota usage. You’re maintaining template-script invariants across a team. You’re recreating retry logic, error reporting, and validation. You’re explaining to a new analyst why the deck looks slightly different this week.

These are real engineering problems, and Apps Script doesn’t pretend to solve them. The script approach is right for the first 5–10 reports. Past that — particularly if the report is brand-critical — the maintenance overhead starts to dominate, and a template-driven platform that handles the generation engine, the orchestration, and the validation pays back fast.

For the architectural treatment, read the Google Slides automation. For the broader document-automation context, the document automation covers where Slides automation fits in the wider production-pipeline picture.

Common questions, answered

Is Apps Script the right way to automate Google Slides? +
For small recurring jobs — under a few hundred slides per run, simple text and image replacement, no chart-data manipulation — Apps Script is the cheapest, fastest path. For higher volume, complex conditional sections, or production-grade reliability, you'll want the Slides API directly or a platform that wraps it.
What's the execution timeout for Apps Script? +
Six minutes per execution for consumer accounts, thirty minutes for Workspace. This is the constraint that ends most ambitious Apps Script projects. Once a generation pass takes more than five minutes, you're either chunking work across multiple invocations or migrating off Apps Script.
Can Apps Script update charts inside Google Slides? +
Only if the chart is linked to a Google Sheet. You update the Sheet via SpreadsheetApp, then call refresh on the linked chart in the deck. Apps Script can't manipulate the chart's underlying data when it's an embedded image or a non-linked chart object.
What's the difference between replaceAllText and an Slides API findReplaceAllText call? +
Functionally similar — both walk the deck and substitute. The Apps Script wrapper is simpler to call but has fewer matching options. The Slides API gives you regex match modes and shape-scoped replacement, which matter once your templates have nested groups or speaker notes you don't want touched.
When should I move off Apps Script to a platform? +
When you're spending more time on Apps Script's edges — quotas, timeouts, deployment, error handling — than on the report itself. The script approach is right for the first 5–10 reports. Past that, the maintenance starts to cost more than a platform subscription.

Related reading

Stop hand-building the same document every cycle.

Tell us what you're trying to automate. We respond within one business day with a real number and a scoping call invitation.

Get Started →