https://github.com/lsvekis/Google-Docs-Style-Resizer-AddOn-Menu-with-Apps-Script

When a Doc has been edited by many people (or copy/pasted from other sources), font sizes can drift all over the place. This quick Google Apps Script adds a custom UI menu to your Google Doc that opens a sidebar where you can set font sizes for:
- Title
- Subtitle
- Heading 1–6
- Normal text
…and then apply those sizes across the whole document (including text inside tables and lists).
What you’ll build
A custom menu in your Doc:
Extensions → Style Resizer → Open Style Resizer
From the sidebar, you choose sizes (e.g., Normal = 12, H1 = 20, H2 = 16…) and click Apply.
Step 1: Create the Apps Script project
- Open your Google Doc
- Go to Extensions → Apps Script
- Create two files:
Code.gsSidebar.html
Step 2: Paste the code
Code.gs
/**
* Adds a custom menu when the document opens.
*/
function onOpen() {
DocumentApp.getUi()
.createMenu('Style Resizer')
.addItem('Open Style Resizer', 'showStyleResizerSidebar')
.addSeparator()
.addItem('Quick: Normal text = 12', 'quickSetNormal12')
.addToUi();
}
/**
* Opens the sidebar UI.
*/
function showStyleResizerSidebar() {
const html = HtmlService.createHtmlOutputFromFile('Sidebar')
.setTitle('Style Resizer');
DocumentApp.getUi().showSidebar(html);
}
/**
* Quick helper: sets NORMAL paragraphs to 12.
*/
function quickSetNormal12() {
applyStyleSizes({
NORMAL: 12
}, false); // includeHeadersFooters = false
}
/**
* Applies sizes across the doc based on paragraph "heading type".
*
* @param {Object} sizeMap e.g. {NORMAL:12, HEADING1:20, TITLE:28, SUBTITLE:16}
* @param {boolean} includeHeadersFooters whether to apply changes to header/footer too
*/
function applyStyleSizes(sizeMap, includeHeadersFooters) {
const doc = DocumentApp.getActiveDocument();
// Validate input sizes (basic safety)
const cleaned = {};
Object.keys(sizeMap || {}).forEach(k => {
const n = Number(sizeMap[k]);
if (Number.isFinite(n) && n >= 6 && n <= 96) cleaned[k] = n;
});
// Apply to body
walkAndResize_(doc.getBody(), cleaned);
// Optionally apply to header/footer (if they exist)
if (includeHeadersFooters) {
const header = doc.getHeader();
const footer = doc.getFooter();
if (header) walkAndResize_(header, cleaned);
if (footer) walkAndResize_(footer, cleaned);
}
}
/**
* Recursively walks container elements and applies font size
* to Paragraph and ListItem text based on their heading.
*/
function walkAndResize_(container, sizeMap) {
if (!container || !container.getNumChildren) return;
for (let i = 0; i < container.getNumChildren(); i++) {
const child = container.getChild(i);
const t = child.getType();
// Paragraphs (includes Title/Subtitle/Headings/Normal)
if (t === DocumentApp.ElementType.PARAGRAPH) {
resizeParagraph_(child.asParagraph(), sizeMap);
continue;
}
// Lists
if (t === DocumentApp.ElementType.LIST_ITEM) {
resizeListItem_(child.asListItem(), sizeMap);
continue;
}
// Tables
if (t === DocumentApp.ElementType.TABLE) {
const table = child.asTable();
for (let r = 0; r < table.getNumRows(); r++) {
const row = table.getRow(r);
for (let c = 0; c < row.getNumCells(); c++) {
walkAndResize_(row.getCell(c), sizeMap);
}
}
continue;
}
// Other container-ish elements (like table cells, body, header, footer)
if (child.getNumChildren) {
walkAndResize_(child, sizeMap);
}
}
}
function resizeParagraph_(paragraph, sizeMap) {
const heading = paragraph.getHeading(); // DocumentApp.ParagraphHeading
const key = headingKey_(heading);
const size = sizeMap[key];
if (!size) return;
// editAsText() applies to text in that paragraph
const text = paragraph.editAsText();
if (text) text.setFontSize(size);
}
function resizeListItem_(listItem, sizeMap) {
// ListItems can still have heading set, but usually NORMAL
const heading = listItem.getHeading();
const key = headingKey_(heading);
const size = sizeMap[key] || sizeMap.NORMAL; // fallback to NORMAL for lists
if (!size) return;
const text = listItem.editAsText();
if (text) text.setFontSize(size);
}
/**
* Converts ParagraphHeading enum to a plain key used by the UI.
*/
function headingKey_(headingEnum) {
switch (headingEnum) {
case DocumentApp.ParagraphHeading.TITLE: return 'TITLE';
case DocumentApp.ParagraphHeading.SUBTITLE: return 'SUBTITLE';
case DocumentApp.ParagraphHeading.HEADING1: return 'HEADING1';
case DocumentApp.ParagraphHeading.HEADING2: return 'HEADING2';
case DocumentApp.ParagraphHeading.HEADING3: return 'HEADING3';
case DocumentApp.ParagraphHeading.HEADING4: return 'HEADING4';
case DocumentApp.ParagraphHeading.HEADING5: return 'HEADING5';
case DocumentApp.ParagraphHeading.HEADING6: return 'HEADING6';
case DocumentApp.ParagraphHeading.NORMAL:
default: return 'NORMAL';
}
}
Sidebar.html
<!DOCTYPE html>
<html>
<head>
<base target="_top">
<style>
body { font-family: Arial, sans-serif; padding: 12px; }
.row { display: flex; justify-content: space-between; align-items: center; margin: 8px 0; }
label { font-size: 13px; }
input[type="number"] { width: 90px; padding: 6px; }
.actions { margin-top: 14px; display: flex; gap: 8px; }
button { padding: 8px 10px; cursor: pointer; }
.note { font-size: 12px; opacity: 0.85; margin-top: 10px; line-height: 1.35; }
.status { margin-top: 10px; font-size: 12px; }
</style>
</head>
<body>
<h3 style="margin: 0 0 8px;">Style Resizer</h3>
<div class="row"><label>Title</label><input id="TITLE" type="number" min="6" max="96" value="28"></div>
<div class="row"><label>Subtitle</label><input id="SUBTITLE" type="number" min="6" max="96" value="16"></div>
<div class="row"><label>Heading 1</label><input id="HEADING1" type="number" min="6" max="96" value="20"></div>
<div class="row"><label>Heading 2</label><input id="HEADING2" type="number" min="6" max="96" value="16"></div>
<div class="row"><label>Heading 3</label><input id="HEADING3" type="number" min="6" max="96" value="14"></div>
<div class="row"><label>Heading 4</label><input id="HEADING4" type="number" min="6" max="96" value="13"></div>
<div class="row"><label>Heading 5</label><input id="HEADING5" type="number" min="6" max="96" value="12"></div>
<div class="row"><label>Heading 6</label><input id="HEADING6" type="number" min="6" max="96" value="12"></div>
<div class="row"><label>Normal text</label><input id="NORMAL" type="number" min="6" max="96" value="12"></div>
<div class="row" style="margin-top: 12px;">
<label>
<input id="includeHF" type="checkbox">
Include header & footer
</label>
</div>
<div class="actions">
<button onclick="apply()">Apply</button>
<button onclick="setDefaults()">Reset defaults</button>
</div>
<div class="note">
Tip: sizes are “Doc font sizes” (e.g., 12 ≈ 12px feel). This updates text inside paragraphs, lists, and tables.
</div>
<div id="status" class="status"></div>
<script>
function setDefaults() {
const defaults = {
TITLE: 28, SUBTITLE: 16,
HEADING1: 20, HEADING2: 16, HEADING3: 14, HEADING4: 13, HEADING5: 12, HEADING6: 12,
NORMAL: 12
};
Object.keys(defaults).forEach(k => document.getElementById(k).value = defaults[k]);
setStatus("Defaults restored.");
}
function apply() {
const keys = ["TITLE","SUBTITLE","HEADING1","HEADING2","HEADING3","HEADING4","HEADING5","HEADING6","NORMAL"];
const map = {};
keys.forEach(k => map[k] = Number(document.getElementById(k).value));
const includeHF = document.getElementById("includeHF").checked;
setStatus("Applying sizes…");
google.script.run
.withSuccessHandler(() => setStatus("Done! Styles resized across the document."))
.withFailureHandler(err => setStatus("Error: " + (err && err.message ? err.message : err)))
.applyStyleSizes(map, includeHF);
}
function setStatus(msg) {
document.getElementById("status").textContent = msg;
}
</script>
</body>
</html>
How to use it
- Reload the Doc (or run
onOpen()once from the editor) - In the Doc: Style Resizer → Open Style Resizer
- Set sizes you want
- Click Apply
Notes and tips
- “px vs pt?” Google Docs uses font size numbers (commonly thought of as pt). In practice, setting 12 gives you the “12px-ish” standard body feel in Docs.
- This script resizes the text inside each paragraph/list item, based on its heading type (Normal, Heading 1, etc.).
- It also handles tables and lists.
- Optional: apply to header/footer too.
Common customization ideas
If you want this to also enforce:
- Font family (e.g., Arial everywhere)
- Line spacing
- Spacing before/after
- Heading colors
…tell me what rules you want and I’ll extend the sidebar to include them.