appscript.dev
Automation Intermediate Docs

Reformat a messy Doc to a style guide

Normalise fonts, headings, and spacing across a Northwind Doc with one function.

Published Aug 17, 2025

A Northwind Doc that has passed through five hands rarely looks like one document. Someone pasted a section from an email and brought its font with it, someone else bumped a heading two sizes larger to make it “stand out”, and the body text drifts between three greys. None of it is wrong, exactly — it is just inconsistent, and inconsistency is what makes a document look unfinished.

This script defines one style guide in code and applies it to every paragraph in a Doc. Body text, H1, H2, and H3 each get a fixed font, size, and colour, so the whole document snaps to the same look in a single pass. It does not touch the words — only how they are presented — which makes it safe to run on a draft right before it goes out.

What you’ll need

  • A Google Doc you want to normalise. The script reads its ID, so have that to hand (it is the long string in the Doc URL).
  • Headings already marked as Heading 1/2/3 in the Doc. The script styles each paragraph by its heading level, so a “heading” that is really just bold body text will be treated as body text.
  • Nothing else — the style guide lives in the script itself.

The script

// The Doc to reformat — the long string from its URL.
const DOC_ID = '1abcDocId';

// The house style. One entry per heading level, plus body text.
// Edit these values to match your own brand.
const STYLE = {
  body: { font: 'Inter', size: 11, color: '#1f2937' },
  h1: { font: 'Inter', size: 24, color: '#0f172a' },
  h2: { font: 'Inter', size: 18, color: '#0f172a' },
  h3: { font: 'Inter', size: 14, color: '#334155' },
};

/**
 * Walks every paragraph in the Doc and applies the matching style from
 * STYLE, based on the paragraph's heading level.
 */
function applyStyleGuide() {
  const body = DocumentApp.openById(DOC_ID).getBody();

  // 1. Read every paragraph in document order.
  const paras = body.getParagraphs();

  if (!paras.length) {
    Logger.log('The Doc has no paragraphs — nothing to reformat.');
    return;
  }

  // 2. For each paragraph, pick the style for its heading level and apply it.
  for (const p of paras) {
    const style = pickStyle(p.getHeading());
    p.setAttributes({
      [DocumentApp.Attribute.FONT_FAMILY]: style.font,
      [DocumentApp.Attribute.FONT_SIZE]: style.size,
      [DocumentApp.Attribute.FOREGROUND_COLOR]: style.color,
    });
  }

  Logger.log('Applied the style guide to ' + paras.length + ' paragraphs.');
}

/**
 * Maps a paragraph's heading level to a style entry. Anything that is
 * not an H1/H2/H3 — normal text, captions, list items — gets body style.
 */
function pickStyle(heading) {
  if (heading === DocumentApp.ParagraphHeading.HEADING1) return STYLE.h1;
  if (heading === DocumentApp.ParagraphHeading.HEADING2) return STYLE.h2;
  if (heading === DocumentApp.ParagraphHeading.HEADING3) return STYLE.h3;
  return STYLE.body;
}

How it works

  1. applyStyleGuide opens the Doc by ID and grabs its body.
  2. It reads every paragraph with getBody().getParagraphs(), which returns them in document order. If the Doc is empty, it logs a message and stops.
  3. For each paragraph it calls pickStyle, passing the paragraph’s heading level. pickStyle returns the matching entry from STYLEh1 for a Heading 1, and so on — or STYLE.body for ordinary text.
  4. It applies that style with setAttributes, setting font family, size, and foreground colour in one call. Setting all three together is faster than three separate calls and avoids half-styled paragraphs if the script stops.
  5. Because every paragraph is visited, mismatched fonts and stray colours are overwritten regardless of how they got there.

Example run

Say the Doc starts out like this — pasted-in formatting everywhere:

ParagraphBeforeAfter
”Quarterly Plan” (Heading 1)Arial, 28pt, blackInter, 24pt, #0f172a
”Budget” (Heading 2)Calibri, 16pt, blueInter, 18pt, #0f172a
”We expect costs to rise…” (body)Times New Roman, 12pt, #555Inter, 11pt, #1f2937
”See the appendix.” (body)Inter, 11pt, redInter, 11pt, #1f2937

After one run, every paragraph matches the house style. The structure and wording are untouched — only the presentation has been normalised.

Run it

This is a tidy-up job you reach for when a Doc needs it, not on a schedule:

  1. In the Apps Script editor, set DOC_ID to your Doc, select applyStyleGuide, and click Run.
  2. Approve the authorisation prompt the first time.
  3. Open the Doc — every heading and paragraph now matches the guide.

To run it from inside the Doc without opening the editor, add a menu:

function onOpen() {
  DocumentApp.getUi()
    .createMenu('Northwind tools')
    .addItem('Apply style guide', 'applyStyleGuide')
    .addToUi();
}

Watch out for

  • It styles by heading level, not by intent. If someone made a “heading” bold instead of marking it as Heading 2, the script treats it as body text. Fix the heading levels in the Doc first, then run this.
  • Fonts must be available. If a font in STYLE is not installed in the user’s Google account, Docs falls back silently to a default — the run still succeeds, but the result will not match. Stick to standard Google Fonts.
  • It does not touch bold, italics, links, or alignment. Those are deliberate inline choices; this script only normalises font, size, and colour. Widen the setAttributes call if you want stricter control.
  • It rewrites every paragraph every run. That is the point, but it means there is no undo beyond the Doc’s own version history — check that before running on a document you cannot easily restore.

Related