appscript.dev
Automation Intermediate Docs Drive

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 , 02 works 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

  1. compileReport opens 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.
  2. If the folder has no Docs it logs and returns null, so an empty folder never produces a blank master report.
  3. It sorts the files by name with localeCompare. This is why a numeric prefix on the section Docs matters: it pins the section order.
  4. It creates a brand-new Doc with DocumentApp.create and grabs its body, the container every section is appended into.
  5. 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, appendListItem or appendTable — so paragraphs, lists and tables all keep their formatting. A page break closes the section.
  6. 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 nameContents
01 Sales summaryTwo paragraphs, one table
02 MarketingThree paragraphs, a bulleted list
03 OperationsFour paragraphs
04 OutlookOne 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 if chain if your sections use anything else.
  • Section order depends entirely on the file names. Without a numeric prefix, localeCompare sorts 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