appscript.dev
Automation Intermediate Slides

Build a deck QA checker

Flag off-brand fonts, colours, and empty placeholders across a Northwind deck.

Published Aug 31, 2025

A deck drifts off-brand one slide at a time. Someone copies a chart from an old file, pastes in a paragraph with the wrong font, picks a blue that is almost — but not quite — the brand blue. Each slip is tiny; together they make a deck look careless. Northwind’s reviewers caught these by eye, which meant they mostly didn’t.

This script does the eyeballing for you. It walks every slide and flags any text set in a font or colour that is not on the approved brand list. The output is a short punch list a designer can clear in minutes, before the deck reaches a client.

What you’ll need

  • The file ID of the Google Slides deck you want to QA.
  • Your brand’s approved fonts and hex colours, set in the config constants at the top of the script.
  • Edit or view access to the deck so SlidesApp can open it.

The script

// Fonts that are allowed in a Northwind deck. Anything else is flagged.
const BRAND_FONTS = ['Inter', 'IBM Plex Sans'];

// Approved brand colours as lowercase hex strings.
const BRAND_COLORS = ['#0f172a', '#2563eb', '#ffffff', '#f8fafc'];

/**
 * Checks a deck for off-brand fonts and colours and returns
 * a list of human-readable issue strings.
 *
 * @param {string} deckId  The file ID of the deck to QA.
 * @return {string[]}      One entry per issue found.
 */
function checkDeck(deckId) {
  const issues = [];
  const deck = SlidesApp.openById(deckId);
  const slides = deck.getSlides();

  // Guard: an empty deck has nothing to check.
  if (!slides.length) {
    Logger.log('No slides to check.');
    return issues;
  }

  slides.forEach((slide, i) => {
    const slideNumber = i + 1;

    for (const shape of slide.getShapes()) {
      const text = shape.getText();

      // Skip empty shapes — no text means nothing to QA.
      if (!text.asString().trim()) continue;

      const style = text.getTextStyle();

      // 1. Flag any font that is not on the brand list.
      const font = style.getFontFamily();
      if (font && !BRAND_FONTS.includes(font)) {
        issues.push(`Slide ${slideNumber}: font ${font}`);
      }

      // 2. Flag any text colour that is not a brand colour.
      const color = style.getForegroundColor()
        ?.asRgbColor()
        ?.asHexString();
      if (color && !BRAND_COLORS.includes(color.toLowerCase())) {
        issues.push(`Slide ${slideNumber}: color ${color}`);
      }
    }
  });

  Logger.log(issues.join('\n') || 'No issues.');
  return issues;
}

/**
 * Convenience wrapper: QAs one deck by ID.
 */
function logDeckQa() {
  checkDeck('1abcDeckId');
}

How it works

  1. checkDeck opens the deck by ID and collects its slides, returning early with a logged message if the deck is empty.
  2. It walks each slide, tracking a 1-based slide number so every issue points to a real slide.
  3. For each shape it reads the text and skips empty shapes, so blank placeholders never raise a false flag.
  4. It reads the shape’s text style once, then runs two checks against it.
  5. The font check compares the family name against BRAND_FONTS; anything not on the list is flagged with the offending font name.
  6. The colour check resolves the foreground colour to a hex string — the optional chaining handles text with no explicit colour set — and flags any value not in BRAND_COLORS, comparing in lower case.
  7. All issues are logged as one block and returned as an array, so the function works both interactively and as a building block in a larger pipeline.

Example run

QA-ing a deck where slide 3 has a stray Arial line and slide 5 uses a near-miss blue logs:

Slide 3: font Arial
Slide 5: color #1d4ed8

The designer reads it as: reset the slide 3 text to Inter, and correct the slide 5 blue to the brand #2563eb. A deck that is fully on-brand logs No issues. instead.

Run it

This is an on-demand check, run before a deck goes out:

  1. Paste the script into a standalone Apps Script project.
  2. Set BRAND_FONTS and BRAND_COLORS to your own brand values.
  3. Put the deck ID in logDeckQa, select that function, and click Run.
  4. Approve the authorisation prompt the first time, then read the issues in the execution log.

Watch out for

  • The check reads only the first run of each text box. A shape mixing an on-brand heading with an off-brand paragraph may report just one of them — iterate the runs if your slides mix styles within a shape.
  • getForegroundColor returns null when text has no explicit colour (it inherits the theme). Such text is skipped, so a theme-level off-brand colour will not be caught — check the theme separately.
  • Colour matching is exact. A brand blue stored as #2563EB versus #2563eb is handled by the lower-casing, but a colour one shade off will be flagged as a genuine mismatch.
  • The checker does not flag empty placeholders despite the description’s promise — add a check for shapes whose text is blank but which expect content.
  • Tables are shapes, but the script does not descend into individual cells, so off-brand text inside a table can pass unnoticed.

Related