Clean Up a Google Doc with Apps Script

Clean Up a Google Doc with Apps Script: Bullets → Dashes, Headings Down a Level, and No More Blank-Line Spam

If you’ve ever copied content into Google Docs from a website, PDF, or another editor, you’ve probably seen the usual mess:

  • Bullets that don’t match your preferred markdown-ish style
  • Heading levels that are all “too big” or inconsistent
  • Random extra blank lines that make the doc look sloppy

This post walks through three simple Google Apps Script functions that clean up an active Google Doc:

  1. Convert bullet list items into plain text lines that start with -
  2. Reduce all heading sizes by one level (H1 → H2, H2 → H3, etc.)
  3. Remove extra blank lines by collapsing repeated empty paragraphs

What this script does (high-level)

These functions operate on the active Google Doc:

  • convertBulletsToDashes()
    Finds list items (bullets), replaces each with a regular paragraph that begins with - , and removes empty bullet lines.
  • reduceHeadingSizes()
    Re-maps headings so everything becomes one level smaller:
    • H1 → H2
    • H2 → H3
    • H3 → H4
    • H4 → H5
    • H5 → H6
  • removeExtraBlankLines()
    Deletes repeated empty paragraphs so you don’t have 2–10 blank lines in a row.

1) Convert bullets to - dashes (great for markdown-style notes)

Why you’d want this

Sometimes you want lists to be plain text (not actual Google Docs bullets). For example:

  • You’re preparing content for Markdown
  • You want consistent “dash lists” for a newsletter draft
  • You’re copying content into another tool that hates Docs list formatting

How it works

The script loops through the document body and looks for elements of type LIST_ITEM. For each list item:

  • If it’s empty → it gets deleted
  • If it has text → it inserts a new paragraph above it: - your text
  • Copies formatting attributes (so it stays visually consistent)
  • Removes the original bullet list item

Important detail: it loops backwards so removing items doesn’t mess up the element indexes.

function convertBulletsToDashes() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();
  const total = body.getNumChildren();

  // Loop backwards to avoid index shifting when removing elements
  for (let i = total - 1; i >= 0; i--) {
    const el = body.getChild(i);

    if (el.getType() !== DocumentApp.ElementType.LIST_ITEM) {
      continue; // skip non-list items
    }

    const listItem = el.asListItem();
    const text = listItem.getText().trim();

    // If it's an empty list bullet → delete it
    if (text === "") {
      body.removeChild(listItem);
      continue;
    }

    // Insert a new paragraph ABOVE the list item
    const newPara = body.insertParagraph(i, "- " + text);

    // Copy formatting (optional but recommended)
    newPara.setAttributes(listItem.getAttributes());

    // Delete the original list item
    body.removeChild(listItem);
  }

  doc.saveAndClose();
}

Tip: This will flatten your bullets into plain paragraphs, so don’t use it if you still want nested list behavior.


2) Reduce heading sizes by one level (fix “everything is H1”)

Why you’d want this

It’s common to import content where headings are oversized—especially if you pasted from:

  • Google Slides
  • Web pages
  • Converted PDFs
  • AI-generated docs that overuse H1/H2

This function makes your document hierarchy less aggressive by shifting each heading down one level.

How it works

A mapping object defines the conversion, then each paragraph is checked:

  • If it’s a paragraph and its heading matches the map → update it
  • Otherwise leave it alone
/**
 * Reduce all headings by one level in the active Google Doc.
 * H1 → H2, H2 → H3, H3 → H4, H4 → H5, H5 → H6
 * Starts from H5 to avoid overwriting styles.
 */
function reduceHeadingSizes() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();
  const totalElements = body.getNumChildren();

  // Mapping of current heading → new heading
  const headingMap = {
    [DocumentApp.ParagraphHeading.HEADING5]: DocumentApp.ParagraphHeading.HEADING6,
    [DocumentApp.ParagraphHeading.HEADING4]: DocumentApp.ParagraphHeading.HEADING5,
    [DocumentApp.ParagraphHeading.HEADING3]: DocumentApp.ParagraphHeading.HEADING4,
    [DocumentApp.ParagraphHeading.HEADING2]: DocumentApp.ParagraphHeading.HEADING3,
    [DocumentApp.ParagraphHeading.HEADING1]: DocumentApp.ParagraphHeading.HEADING2
  };

  for (let i = 0; i < totalElements; i++) {
    const element = body.getChild(i);

    if (element.getType() === DocumentApp.ElementType.PARAGRAPH) {
      const paragraph = element.asParagraph();
      const currentHeading = paragraph.getHeading();

      if (headingMap[currentHeading]) {
        paragraph.setHeading(headingMap[currentHeading]);
      }
    }
  }

  DocumentApp.getUi().alert('All headings have been reduced by one level.');
}

Note: This does not change normal text (it only affects headings).


3) Remove extra blank lines (collapse repeated empties)

Why you’d want this

Docs often accumulate “empty paragraph spam” after editing or copying/pasting. This makes scrolling annoying and ruins the layout.

This function collapses multiple blank paragraphs into just one.

How it works

It loops backwards through paragraphs and tracks whether the previous paragraph was empty:

  • If current paragraph is empty and previous was empty → delete it
  • Otherwise keep it
  • Any non-paragraph element resets the “empty” tracking (tables/images can break sequences)
/**
 * Removes extra blank lines from the active Google Doc.
 * Collapses multiple consecutive empty paragraphs into a single one.
 */
function removeExtraBlankLines() {
  const doc = DocumentApp.getActiveDocument();
  const body = doc.getBody();

  let previousWasEmpty = false;

  // Loop backwards so deletions don't affect indexes
  for (let i = body.getNumChildren() - 1; i >= 0; i--) {
    const element = body.getChild(i);

    if (element.getType() !== DocumentApp.ElementType.PARAGRAPH) {
      previousWasEmpty = false;
      continue;
    }

    const paragraph = element.asParagraph();
    const text = paragraph.getText().trim();

    if (text === '') {
      if (previousWasEmpty) {
        body.removeChild(element); // Remove extra blank line
      }
      previousWasEmpty = true;
    } else {
      previousWasEmpty = false;
    }
  }

  DocumentApp.getUi().alert('Extra blank lines have been removed.');
}

How to use these in Google Docs

  1. Open your Google Doc
  2. Go to Extensions → Apps Script
  3. Paste the code into the script editor
  4. Save
  5. Run one function at a time (you’ll be prompted to authorize)

Suggested run order (common cleanup flow):

  1. removeExtraBlankLines()
  2. reduceHeadingSizes()
  3. convertBulletsToDashes() (only if you want to flatten lists)