Google Doc automation - append weekly report with a template
Google Apps Script automation that inserts content from a source Google Doc into another Google Doc after a specified phrase.
- The Google Doc with the template will be referred to as the "source" Google Doc.
- The Google Doc that will receive the template will be referred to as the "target" Google Doc.
Table of contents
What this automation does
- The script inserts the content of one Google Doc (the source) into another (the target) after a specific phrase.
- It uses the Google Apps Script's DocumentApp service to interact with Google Docs.
- You can configure the script to run on a weekly basis.
Google Apps Script code
js
/**
* Inserts content from a source Google Doc into a target Google Doc after a specified phrase.
* Versions:
* + 1.0.4 (2025-03-13): Addressed teh list format issue
* + 1.0.2 (2025-03-13): Polished code; added more element-specific handling.
*
* @param {string} srcDocId - Source Google Doc's ID.
* @param {string} targetDocId - Target Google Doc's ID.
* @param {string} insertLocation - Phrase in the target Google Doc after which content will be inserted.
* @param {string} separator - Optional separator text for clarity.
*/
function insertDocContentAfterPhrase() {
const srcDocId = "SOURCE_GOOGLE_DOC_ID";
const targetDocId = "TARGET_GOOGLE_DOC_ID";
const insertLocation = "Insert Below:";
const separator = "----------------------";
const srcDoc = DocumentApp.openById(srcDocId).getBody();
const targetBody = DocumentApp.openById(targetDocId).getBody();
// Find the paragraph index that contains the insert location phrase.
const targetParagraphs = targetBody.getParagraphs();
const targetIndex = targetParagraphs.findIndex((paragraph) =>
paragraph.getText().includes(insertLocation)
);
if (targetIndex === -1) {
Logger.log("Phrase not found in the target document.");
return;
}
let insertIndex = targetIndex + 1;
// Insert a separator for clarity
targetBody.insertParagraph(insertIndex, separator);
insertIndex++;
// Build an array of source elements using Array.from.
const srcElements = Array.from({ length: srcDoc.getNumChildren() }, (_, j) =>
srcDoc.getChild(j)
);
// Helper function to insert elements based on type.
const insertElement = (element, index) => {
switch (element.getType()) {
case DocumentApp.ElementType.INLINE_IMAGE:
targetBody.insertImage(index, element.copy());
break;
case DocumentApp.ElementType.PAGE_BREAK:
targetBody.insertPageBreak(index, element.copy());
break;
case DocumentApp.ElementType.PARAGRAPH:
targetBody.insertParagraph(index, element.copy());
break;
case DocumentApp.ElementType.TABLE:
targetBody.insertTable(index, element.copy());
break;
case DocumentApp.ElementType.LIST_ITEM: {
const newListItem = targetBody.insertListItem(index, element.getText());
newListItem.setAttributes(element.getAttributes());
newListItem.setGlyphType(element.getGlyphType());
break;
}
default:
console.log("Element type not supported: " + element.getType());
targetBody.insertParagraph(index, element.copy()); // Insert as a paragraph by default.
break;
}
};
// Insert each source element into the target document.
srcElements.forEach((element) => {
insertElement(element, insertIndex);
insertIndex++;
});
Logger.log("Content inserted successfully after the target phrase.");
}
Setup instructions
- Create a Google Apps Script project
- Open Google Drive → Click + New → More → Google Apps Script.
- Paste the script above.
- Add the Google Doc IDs
- Replace the placeholders
SOURCE_GOOGLE_DOC_ID
andTARGET_GOOGLE_DOC_ID
with the actual Google Doc IDs. - The Google Doc ID is the unique identifier found in the URL of the document. For example, in
https://docs.google.com/document/d/1_h0aQdM1mBSZawk2stu9Ng_TCZm4UvsFJ9y5prYuCtU/edit
, the ID is1_h0aQdM1mBSZawk2stu9Ng_TCZm4UvsFJ9y5prYuCtU
.
- Replace the placeholders
- Replace the target phrase
- Replace
Insert Below:
with the phrase in the target Google Doc where you want to insert the template.
- Replace
- Save the script
- Click File → Save.
- Set up a trigger to run weekly
- Click on Triggers (clock icon in Apps Script).
- Click + Add Trigger.
- Choose function:
insertDocContentAfterPhrase
- Choose event source: Time-driven
- Choose type: Week timer
- Select a day and time to run.
IMPORTANT
Google Doc permissions: Ensure your script has permission to access and modify both the source and target documents. Set the sharing permissions accordingly before running the script.
Code explanation
Variables
srcDocId
: The source Google Doc's unique identifier.targetDocId
: The target Google Doc's unique identifier.insertLocation
: A case-sensitive string in the target document where the new content will be inserted below. If there are multiple instances of this phrase, the content will be inserted after the first occurrence.separator
: A hard-coded value used as a visual separator (optional). The code inserts an empty paragraph instead of the separator text for clarity.
Extract content from the source and target Google Docs
javascript
const srcDoc = DocumentApp.openById(srcDocId).getBody();
const targetBody = DocumentApp.openById(targetDocId).getBody();
- The identifiers provided in the
srcDocId
andtargetDocId
variables are used to locate these Google Docs files. - Using the
DocumentApp
service, the function opens both the source and target Google Docs. - The
getBody()
method retrieves the main content (body) of each document, which contains all the paragraphs, tables, and other elements that make up the document.
Verify and find the insertion point
javascript
const targetParagraphs = targetBody.getParagraphs();
const targetIndex = targetParagraphs.findIndex((paragraph) =>
paragraph.getText().includes(insertLocation)
);
if (targetIndex === -1) {
Logger.log("Phrase not found in the target document.");
return;
}
let insertIndex = targetIndex + 1;
- Use the
getParagraphs()
method to retrieve an array of all paragraphs in the target document. - Use the
findIndex()
method to locate the paragraph that contains the specified marker phrase (insertLocation
). If the phrase is not found, log an error message and exit the function. - If the phrase is found, calculate the insertion point (
insertIndex
) as the paragraph immediately following the one that contains the marker.
IMPORTANT
insertLocation
value is a case-sensitive string that must match the exact text in the target document. Ensure that the marker phrase is correctly specified to avoid errors.
Inserting a separator and building the source elements array
javascript
// Insert a separator for clarity
targetBody.insertParagraph(insertIndex, separator);
insertIndex++;
// Build an array of source elements using Array.from.
const srcElements = Array.from({ length: srcDoc.getNumChildren() }, (_, j) =>
srcDoc.getChild(j)
);
- Insert an empty paragraph along with the string stored in the
separator
variable at the insertion point to visually separate the existing content from the new content. - Use the
getNumChildren()
method to determine the number of elements in the source document. - Use the
Array.from()
method to create an array of copied elements from the source document. Thecopy()
method is used to create a duplicate of each element, ensuring that the original source document is not modified.
TIP
Add .setBold(true)
or .setUnderline(true)
to format the separator line for better visibility.
Insert copied content into the target document
javascript
const insertElement = (element, index) => {
switch (element.getType()) {
case DocumentApp.ElementType.INLINE_IMAGE:
targetBody.insertImage(index, element.copy());
break;
case DocumentApp.ElementType.PAGE_BREAK:
targetBody.insertPageBreak(index, element.copy());
break;
case DocumentApp.ElementType.PARAGRAPH:
targetBody.insertParagraph(index, element.copy());
break;
case DocumentApp.ElementType.TABLE:
targetBody.insertTable(index, element.copy());
break;
case DocumentApp.ElementType.LIST_ITEM: {
const newListItem = targetBody.insertListItem(index, element.getText());
newListItem.setAttributes(element.getAttributes());
newListItem.setGlyphType(element.getGlyphType());
break;
}
default:
console.log("Element type not supported: " + element.getType());
targetBody.insertParagraph(index, element.copy()); // Insert as a paragraph by default.
break;
}
};
- Iterate over each copied element from the source document using the
forEach()
method. - Use the
getType()
method to determine the type of each element (paragraph, table, or list item). - Use the
switch
statement to handle each element type accordingly:- If the element is an inline image, insert it into the target document using
insertImage()
. - If the element is a page break, insert it using
insertPageBreak()
. - If the element is a paragraph, insert it into the target document using
insertParagraph()
. - If the element is a table, insert it using
insertTable()
. - If the element is a list item, insert it using
insertListItem()
. To avoid losing formatting, the script copies the attributes and glyph type from the source list item to the newly inserted list item.
- If the element is an inline image, insert it into the target document using
- Insert as a paragraph by default.
- The helper function
insertElement
checks each element's type and uses the appropriate insertion method. It handles inline images, page breaks, paragraphs, tables, and list items. For list items, it applies additional formatting.
Inserting each element into the target document
javascript
srcElements.forEach((element) => {
insertElement(element, insertIndex);
insertIndex++;
});
- Iterate over each element in the
srcElements
array. - For each element, call the
insertElement()
function to insert it into the target document at the specified index. - Increment the
insertIndex
after inserting each element to ensure that the elements are inserted in the correct order.
Log a success message
javascript
Logger.log("Content inserted successfully after the target phrase.");
- After all elements are inserted into the target document, a log message is generated to indicate that the content has been inserted successfully.