// Docs · Apps Script

Find and replace text in a Google Doc in Google Docs.

How to use Apps Script's replaceText to find and replace text in a Google Doc, including the critical detail that the first argument is a regex pattern — so dots, parentheses, and dollar signs must be escaped.

I want to programmatically find and replace text in a Google Doc using Apps Script, but my replacements are matching the wrong characters because I forgot replaceText takes a regex.

The script

copy · paste · trigger
replaceInDoc.gs
Apps Script
// Replace text in the active Google Doc
// Note: first arg to replaceText is a regex pattern — escape special chars
function replaceInDoc() {
  var body = DocumentApp.getActiveDocument().getBody();

  // Safe: plain alphanumeric strings need no escaping
  body.replaceText('Hello World', 'Hi There');

  // Unsafe example: 'v1.0' matches 'v100' because . is regex wildcard
  // body.replaceText('v1.0', 'v2.0'); // DON'T do this

  // Correct: escape the dot with a double backslash
  body.replaceText('v1\.0', 'v2.0');

  // Replace a bracketed placeholder like [NAME]
  body.replaceText('\[NAME\]', 'Jordan');

  // Replace a dollar-sign token like $TOTAL
  body.replaceText('\$TOTAL', '1,240.00');
}

Need a variant? Gnaw writes a custom version from one sentence — fields, triggers, edge cases handled.

Walkthrough

The one thing the docs bury

Body.replaceText(searchPattern, replacement) is the right method, and it works on the entire body including headers if you use the full document range. What the official reference mentions only briefly is that searchPattern is a Java-flavored regular expression, not a plain string. That distinction is invisible when your search term is something like 'John Smith', but it will ruin your day the moment your template uses dots, brackets, or dollar signs.

The first time I hit this, I was replacing version strings like 'v1.0' across a doc and ended up replacing 'v100' and 'v1X0' as well — because the unescaped dot matched any character. The fix is a double backslash before each special character inside the JavaScript string: 'v1\.0' becomes the regex v1\.0, which matches only a literal dot.

Characters that must be escaped

The full list of regex metacharacters is: . * + ? ^ $ { } [ ] | ( ) and the backslash itself. In practice, the ones that appear most often in document templates are the dot (version numbers, domain names), square brackets (placeholders like [CLIENT_NAME]), and the dollar sign (currency tokens like $AMOUNT or $TOTAL).

Each gets a double backslash in your Apps Script string: '\.' for a literal dot, '\[' and '\]' for literal brackets, '\$' for a literal dollar sign. The first backslash escapes the second inside the JavaScript string, so what the regex engine sees is the single-backslash escape sequence it expects.

If you are building the search string dynamically from user input or a spreadsheet column, wrap it in a helper: function escapeRegex(s) { return s.replace(/[.*+?^${}()|\[\]\\]/g, '\\$&'); }. Pass every user-supplied value through that before handing it to replaceText.

Replacing across headers, footers, and footnotes

getBody() covers only the main body. If your document uses headers or footers that also contain the text you want to replace, you need to call replaceText on those sections separately.

For the header: DocumentApp.getActiveDocument().getHeader().replaceText(pattern, replacement). For the footer, swap getHeader() for getFooter(). Both can be null if the document has no header or footer, so wrap each call in a null check: var header = doc.getHeader(); if (header) { header.replaceText(pattern, replacement); }

Footnotes are a separate collection. Iterate with doc.getFootnotes() and call getText() / insertText() manually — replaceText is not available directly on footnote elements in the same way. That covers essentially every case a real document template will throw at you.

Want a custom version?

Describe your sheet and the rule you want. Gnaw writes the Apps Script — fields, triggers, edge cases — in one shot.

FAQ

4 questions
Why is replaceText replacing more than I expected?
Your search string contains a regex metacharacter — most likely a dot, bracket, or dollar sign — that is matching more broadly than a literal character would. Escape each metacharacter with a double backslash: 'v1\.0' instead of 'v1.0'.
Does replaceText work on the whole document or just the body?
Just the body. Headers, footers, and footnotes are separate objects. Call replaceText on each one individually after getting it with getHeader(), getFooter(), or by iterating getFootnotes().
Can I do a case-insensitive find and replace?
Yes, use the regex inline flag: body.replaceText('(?i)hello', 'Hi'). The (?i) prefix tells the Java regex engine to ignore case for the rest of the pattern. The replacement string is literal, not a regex, so no escaping is needed there.
How do I replace text in all open documents, not just the active one?
You can only open documents your script has access to via DocumentApp.openById(id). There is no API to enumerate all open documents in a browser session. To batch-replace across many docs, keep a list of document IDs (in a spreadsheet, say) and loop over them with openById, calling replaceText on each body in turn.