Index every heading across a folder of Docs
Build a searchable outline of all documents in a folder — find any section by name.
Published Sep 21, 2025
Northwind’s knowledge-base/ folder holds around 50 Docs — process notes, style
guides, onboarding pages. The content is good, but finding the right section
means opening Docs one by one and scanning their outlines. There is no single
place to search “where do we cover invoicing?” and get an answer.
This script builds that place. It walks every Doc in a folder, pulls out each heading paragraph with its level and a link back to the source, and writes the lot to a sheet. The result is a flat, filterable outline of the whole knowledge base — sort it, search it, or filter to “Heading 1” for a table of contents across all 50 documents at once.
What you’ll need
- A Drive folder of Google Docs to index, and its folder ID.
- A Google Sheet to write the index into, and its sheet ID. The script writes to the first tab and clears it first, so use a dedicated sheet.
- Read access to the Docs and edit access to the sheet.
The script
// The Drive folder whose Docs should be indexed.
const FOLDER_ID = '1abcKnowledgeBaseFolderId';
// The Sheet the heading index is written to.
const INDEX_SHEET_ID = '1abcIndexSheetId';
// The header row written above the index.
const HEADERS = ['Doc', 'Level', 'Heading', 'Link'];
/**
* Indexes every heading in every Doc of a folder into a sheet.
*
* @param {string} folderId The folder of Docs to scan.
* @param {string} sheetId The Sheet to write the index into.
*/
function indexHeadings(folderId, sheetId) {
// 1. Open the folder and grab an iterator of its Google Docs.
const files = DriveApp.getFolderById(folderId)
.getFilesByType(MimeType.GOOGLE_DOCS);
const rows = [];
// 2. Walk every Doc in the folder.
while (files.hasNext()) {
const file = files.next();
const paragraphs = DocumentApp.openById(file.getId())
.getBody()
.getParagraphs();
// 3. Walk every paragraph, keeping only the headings.
for (const p of paragraphs) {
const h = p.getHeading();
if (h === DocumentApp.ParagraphHeading.NORMAL) continue;
// 4. Pull the numeric level out of the heading enum name.
const level = h.toString().match(/HEADING(\d)/)?.[1] || '?';
// 5. Record the Doc name, level, heading text, and a link back.
rows.push([file.getName(), level, p.getText(), file.getUrl()]);
}
}
// 6. Bail out if no headings were found anywhere.
if (!rows.length) {
Logger.log('No headings found in this folder — nothing to index.');
return;
}
// 7. Rebuild the index sheet from scratch and write every heading.
const sheet = SpreadsheetApp.openById(sheetId).getSheets()[0];
sheet.clear();
sheet.getRange(1, 1, 1, HEADERS.length).setValues([HEADERS]);
sheet.getRange(2, 1, rows.length, HEADERS.length).setValues(rows);
Logger.log('Indexed ' + rows.length + ' headings.');
}
How it works
indexHeadingsopens the folder and asks for an iterator filtered to Google Docs only, so spreadsheets and PDFs in the folder are ignored.- It loops over every Doc, opening each one and reading its body’s paragraphs.
- For each paragraph it calls
getHeading(). Anything that comes back asNORMALis body text and is skipped — only true headings make the index. - The heading enum stringifies to names like
HEADING2. A small regex pulls the digit out so the index records a plain2rather than the enum name.TITLEandSUBTITLEheadings have no digit, so they record as?. - It pushes one row per heading: the Doc name, the level, the heading text,
and
file.getUrl()— the link that lets you jump straight to the source Doc. - If no headings turned up across the whole folder it logs and stops, rather than clearing the sheet for nothing.
- It clears the first tab, writes the header row, and writes every collected
heading in one
setValuescall.
Example run
Say knowledge-base/ contains two Docs. “Invoicing process” has a Heading 1 and
two Heading 2s; “Onboarding guide” has a Heading 1 and one Heading 2. After a
run the index sheet holds:
| Doc | Level | Heading | Link |
|---|---|---|---|
| Invoicing process | 1 | Raising an invoice | docs.google.com/… |
| Invoicing process | 2 | Approval steps | docs.google.com/… |
| Invoicing process | 2 | Chasing late payment | docs.google.com/… |
| Onboarding guide | 1 | First day | docs.google.com/… |
| Onboarding guide | 2 | Accounts and access | docs.google.com/… |
Now a search for “payment” finds the section instantly, and filtering the Level
column to 1 gives a top-level map of the whole knowledge base.
Trigger it
The knowledge base changes slowly, so a nightly rebuild keeps the index fresh without any manual effort:
- In the Apps Script editor open Triggers (the clock icon).
- Click Add Trigger.
- Choose
indexHeadings, event source Time-driven, type Day timer, and a quiet hour such as 2am to 3am.
Because the function takes folderId and sheetId as arguments, a trigger
needs a zero-argument wrapper:
/**
* Trigger entry point — indexes the configured folder into the index sheet.
*/
function indexHeadingsScheduled() {
indexHeadings(FOLDER_ID, INDEX_SHEET_ID);
}
Point the trigger at indexHeadingsScheduled instead of indexHeadings.
Watch out for
- Opening every Doc is slow. Around 50 Docs is comfortable, but a few hundred may bump into the six-minute execution limit — index in batches by sub-folder if the folder keeps growing.
- The script reads the folder’s direct children only. Docs in sub-folders are not indexed; recurse into sub-folders explicitly if your knowledge base is nested.
getUrl()links to the Doc, not to the specific heading. Apps Script cannot produce a deep link to a heading, so the index gets you to the right document — the reader scrolls from there.TITLEandSUBTITLEparagraphs index with a level of?. If you only want Heading 1–6, filter those rows out before pushing them.
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