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
applyStyleGuideopens the Doc by ID and grabs its body.- It reads every paragraph with
getBody().getParagraphs(), which returns them in document order. If the Doc is empty, it logs a message and stops. - For each paragraph it calls
pickStyle, passing the paragraph’s heading level.pickStylereturns the matching entry fromSTYLE—h1for a Heading 1, and so on — orSTYLE.bodyfor ordinary text. - 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. - 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:
| Paragraph | Before | After |
|---|---|---|
| ”Quarterly Plan” (Heading 1) | Arial, 28pt, black | Inter, 24pt, #0f172a |
| ”Budget” (Heading 2) | Calibri, 16pt, blue | Inter, 18pt, #0f172a |
| ”We expect costs to rise…” (body) | Times New Roman, 12pt, #555 | Inter, 11pt, #1f2937 |
| ”See the appendix.” (body) | Inter, 11pt, red | Inter, 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:
- In the Apps Script editor, set
DOC_IDto your Doc, selectapplyStyleGuide, and click Run. - Approve the authorisation prompt the first time.
- 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
STYLEis 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
setAttributescall 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
Generate personalized study guides from notes
Reformat raw notes into structured study guides — for Northwind's internal training programme.
Updated Feb 8, 2026
Build a contract-clause assembly system
Construct Northwind agreements from a library of approved clauses — drag-drop in code.
Updated Feb 1, 2026
Translate and resolve Doc comments
Localise reviewer feedback on a shared Doc so multilingual teams can collaborate.
Updated Jan 25, 2026
Auto-archive finalized Docs to dated folders
File completed Northwind Docs by month so the active folder stays focused on in-flight work.
Updated Jan 18, 2026
Build a fillable intake form inside a Doc
Create structured intake forms with placeholder fields readers can fill — for client briefs.
Updated Jan 11, 2026