Compile many Docs into one master report
Concatenate sections from a folder of Docs into a single combined document.
Published Sep 7, 2025
Northwind’s quarterly review is not one document — it is eight. Each team drafts its own section as a separate Doc, and at the end of the quarter someone has to stitch them into a single report. Copy-pasting between Docs loses formatting, breaks lists and tables, and takes the best part of a morning.
This script does the stitching properly. It collects every Doc in a folder, sorts them by name so the section order is predictable, and appends each one into a fresh master document — paragraphs, list items and tables copied element by element so the formatting survives. Each section gets a Heading 1 with the source Doc’s name and a page break after it.
What you’ll need
- A Drive folder containing the section Docs, and its folder ID. Name the Docs
so they sort into the order you want — a numeric prefix like
01,02works well. - Read access to the section Docs. The script creates the master report itself, so there is nothing to set up for the output.
The script
// The Drive folder holding the section Docs to compile.
const FOLDER_ID = '1abcReviewFolderId';
// The name given to the compiled master document.
const OUTPUT_NAME = 'Northwind quarterly review — compiled';
/**
* Compiles every Doc in a folder into one master report.
*
* @param {string} folderId The folder of section Docs to compile.
* @param {string} outputName The name for the compiled master Doc.
* @return {string} The URL of the compiled master Doc.
*/
function compileReport(folderId, outputName) {
const folder = DriveApp.getFolderById(folderId);
// 1. Collect every Google Doc in the folder into an array.
const files = [];
const it = folder.getFilesByType(MimeType.GOOGLE_DOCS);
while (it.hasNext()) files.push(it.next());
// 2. Bail out if the folder has no Docs to compile.
if (!files.length) {
Logger.log('No Docs in this folder — nothing to compile.');
return null;
}
// 3. Sort by name so the section order is predictable.
files.sort((a, b) => a.getName().localeCompare(b.getName()));
// 4. Create the master Doc and grab its body.
const master = DocumentApp.create(outputName);
const body = master.getBody();
// 5. Append each section Doc into the master.
for (const f of files) {
// 5a. Start the section with a Heading 1 carrying the source name.
body.appendParagraph(f.getName())
.setHeading(DocumentApp.ParagraphHeading.HEADING1);
// 5b. Copy each child element across, preserving its type.
const src = DocumentApp.openById(f.getId()).getBody();
for (let i = 0; i < src.getNumChildren(); i++) {
const c = src.getChild(i).copy();
const type = c.getType();
if (type === DocumentApp.ElementType.PARAGRAPH) {
body.appendParagraph(c);
} else if (type === DocumentApp.ElementType.LIST_ITEM) {
body.appendListItem(c);
} else if (type === DocumentApp.ElementType.TABLE) {
body.appendTable(c);
}
}
// 5c. End the section with a page break.
body.appendPageBreak();
}
// 6. Save the master and return its URL.
master.saveAndClose();
Logger.log('Compiled ' + files.length + ' Docs into "' + outputName + '".');
return master.getUrl();
}
How it works
compileReportopens the folder and collects every Google Doc into an array. An array — rather than working straight off the iterator — is what lets the next step sort them.- If the folder has no Docs it logs and returns
null, so an empty folder never produces a blank master report. - It sorts the files by name with
localeCompare. This is why a numeric prefix on the section Docs matters: it pins the section order. - It creates a brand-new Doc with
DocumentApp.createand grabs its body, the container every section is appended into. - For each section Doc it appends a Heading 1 with the source name, then walks
the source body’s children. Each child is
copy()-ed and re-appended with the matching method —appendParagraph,appendListItemorappendTable— so paragraphs, lists and tables all keep their formatting. A page break closes the section. - It saves the master and returns its URL so a caller, or a menu wrapper, can link straight to the finished report.
Example run
Say the review folder holds four Docs, named so they sort cleanly:
| File name | Contents |
|---|---|
| 01 Sales summary | Two paragraphs, one table |
| 02 Marketing | Three paragraphs, a bulleted list |
| 03 Operations | Four paragraphs |
| 04 Outlook | One paragraph |
After a run, the master Doc “Northwind quarterly review — compiled” contains, in order: a 01 Sales summary Heading 1, that Doc’s content, a page break; a 02 Marketing Heading 1, its content, a page break; and so on. Each section starts on its own page with a clear title, and the URL of the finished report is returned in the log.
Run it
Compiling the report is a once-a-quarter job, so run it by hand. Add a wrapper that passes the config values:
/**
* Run entry point — compiles the configured folder into a master report.
*/
function compileReportNow() {
const url = compileReport(FOLDER_ID, OUTPUT_NAME);
Logger.log('Master report: ' + url);
}
Select compileReportNow, click Run, approve the authorisation prompt the
first time, then open the URL from the log to read the compiled report.
Watch out for
- Only paragraphs, list items and tables are copied. Inline images sit inside
paragraphs and come across, but a Doc with other element types — or images as
positioned objects — may lose content. Extend the
ifchain if your sections use anything else. - Section order depends entirely on the file names. Without a numeric prefix,
localeComparesorts alphabetically, which is rarely the order you want. - Re-running creates a new master Doc every time. The old one is not replaced —
delete stale copies, or open a fixed Doc by ID and clear its body instead of
calling
create. - A folder of large Docs can be slow to copy. If you approach the six-minute execution limit, compile in two passes and merge the halves.
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