appscript.dev
Automation Intermediate Drive Docs

Keep a self-updating contents file per folder

Auto-create a `_contents.md` Doc inside every Northwind folder, refreshed nightly.

Published Nov 13, 2025

Northwind’s project folders fill up over the life of a job — briefs, drafts, exports, contracts — and the only way to see what is inside one is to open it in Drive and scroll. That is fine until someone needs a quick overview without Drive access, or wants a printable index of a finished project, or is browsing the folder structure on their phone.

This script drops a _contents Doc into every folder it walks and keeps it current. The Doc lists every file in that folder as a clickable link, refreshed nightly so it never drifts out of date. Each folder ends up self-documenting: open the contents Doc and you see the folder without opening the folder.

What you’ll need

  • A root folder to index. The script walks it and every subfolder, and writes a contents Doc into each one.
  • The folder’s ID — the string in its URL after /folders/.
  • Nothing else. The script creates each _contents Doc itself using the Docs service, which every Google account has.

The script

// The Drive folder to index. The script recurses into every subfolder.
const ROOT_FOLDER_ID = '1abcRootFolderId';

// The name of the contents Doc placed inside each folder. It is also
// skipped when listing files, so it never lists itself.
const CONTENTS_DOC_NAME = '_contents';

/**
 * Entry point. Walks the root folder and refreshes a contents Doc
 * inside it and every subfolder beneath it.
 */
function refreshContentsFiles() {
  walk(DriveApp.getFolderById(ROOT_FOLDER_ID));
}

/**
 * Builds or refreshes the contents Doc for one folder, then recurses
 * into its subfolders.
 */
function walk(folder) {
  // 1. Find an existing contents Doc, or create a fresh one.
  const existing = folder.getFilesByName(CONTENTS_DOC_NAME);
  const hadDoc = existing.hasNext();
  const doc = hadDoc
    ? DocumentApp.openById(existing.next().getId())
    : DocumentApp.create(CONTENTS_DOC_NAME);

  // 2. A newly created Doc lands in My Drive — move it into this folder.
  if (!hadDoc) DriveApp.getFileById(doc.getId()).moveTo(folder);

  // 3. Clear the Doc and give it a title naming the folder.
  const body = doc.getBody();
  body.clear();
  body.appendParagraph('Contents of ' + folder.getName())
    .setHeading(DocumentApp.ParagraphHeading.TITLE);

  // 4. List every file in the folder as a linked bullet, skipping the
  //    contents Doc itself.
  let count = 0;
  const files = folder.getFiles();
  while (files.hasNext()) {
    const f = files.next();
    if (f.getName() === CONTENTS_DOC_NAME) continue;
    body.appendListItem('').appendText(f.getName()).setLinkUrl(f.getUrl());
    count++;
  }

  // 5. Note an empty folder explicitly so the Doc is never blank.
  if (count === 0) body.appendParagraph('(empty folder)');
  doc.saveAndClose();

  // 6. Recurse into every subfolder.
  const subs = folder.getFolders();
  while (subs.hasNext()) walk(subs.next());
}

How it works

  1. refreshContentsFiles is the entry point — it just calls walk on the root folder.
  2. walk looks for an existing _contents Doc in the folder. If one exists it reuses it; if not it creates a new Doc, which keeps the same URL across runs so links to it stay stable.
  3. A freshly created Doc is born in My Drive, so the script moves it into the folder it belongs to.
  4. It clears the Doc body and writes a title naming the folder, so the Doc reads well even printed on its own.
  5. It walks the folder’s files and adds each as a bulleted, linked item — skipping the contents Doc so it never lists itself. An empty folder gets an explicit “(empty folder)” line instead of a blank page.
  6. It saves the Doc, then recurses into every subfolder so the whole tree ends up indexed.

Example run

Take a project folder Acme — Q3 campaign holding three files. After a run, the _contents Doc inside it reads:

Contents of Acme — Q3 campaign

  • Brief.gdoc
  • Brand deck v4.pptx
  • Shoot schedule.gsheet

Each bullet is a live link straight to the file. A subfolder Acme — Q3 campaign/Exports gets its own _contents Doc listing only that subfolder’s files — every level of the tree is documented independently.

Trigger it

Run this nightly so each folder’s contents Doc reflects the day’s changes:

  1. In the Apps Script editor, open Triggers (the clock icon).
  2. Click Add Trigger.
  3. Choose refreshContentsFiles, event source Time-driven, type Day timer, and a quiet hour such as 2am to 3am.
  4. Save, and approve the authorisation prompt the first time.

Watch out for

  • The script rebuilds each Doc from scratch every run. Do not hand-edit a _contents Doc expecting your notes to survive — anything you add is wiped on the next refresh.
  • It lists files but not subfolders. Each subfolder gets its own contents Doc instead; if you want subfolders named in the parent Doc too, add a second loop over folder.getFolders().
  • Creating and opening a Doc per folder is relatively slow. A few hundred folders is comfortable; many thousands may approach the six-minute runtime limit. If you hit it, index one subtree per run.
  • Every refresh counts as an edit on every Doc, so the contents Docs themselves will appear in any Drive activity report. That is expected — just be aware they add noise to audits.
  • The Docs created here are real Google Docs, despite the .md in the article title. There is no native Markdown file type in Drive; a Doc is the closest printable, linkable equivalent.

Related