Generate release notes from a changelog sheet
Format Northwind's changelog rows into a published-ready Doc per version.
Published Nov 16, 2025
Northwind’s engineers log every shipped change in a Changelog sheet — one row
per item, tagged as a feature, a fix, or a breaking change. The data is tidy,
but a spreadsheet is not what you hand to customers when a version ships. Someone
still has to copy the rows out, group them, and format a release notes document.
This script does that grouping for you. Give it a version number and it pulls every changelog row for that version, sorts the items into the three sections customers expect — breaking changes first, then features, then fixes — and writes a clean Google Doc ready to publish. It is a small job, but it is the kind of small job that gets skipped under deadline pressure.
What you’ll need
- A
ChangelogGoogle Sheet with a header row and three columns:version,type(one offeat,fix, orbreaking), anddescription. - Nothing else — the script creates the release notes Doc itself.
The script
// The sheet that holds your changelog rows.
const CHANGELOG_SHEET_ID = '1abcChangelogId';
// The order sections appear in the Doc, and the heading shown for each.
// Breaking changes come first because that is what customers must read.
const SECTIONS = [
{ type: 'breaking', heading: 'Breaking changes' },
{ type: 'feat', heading: 'Features' },
{ type: 'fix', heading: 'Fixes' },
];
/**
* Reads every changelog row for one version and writes a grouped
* release notes Doc. Returns the URL of the new document.
*/
function buildReleaseNotes(version) {
// 1. Read the changelog and keep only rows for the requested version.
const items = readSheet(CHANGELOG_SHEET_ID)
.filter((r) => r.version === version);
// 2. Bail out early if nothing matches — no point creating an empty Doc.
if (items.length === 0) {
throw new Error(`No changelog entries found for version ${version}.`);
}
// 3. Create the Doc and give it a title heading.
const doc = DocumentApp.create(`Release notes — ${version}`);
const body = doc.getBody();
body.appendParagraph(`Release ${version}`)
.setHeading(DocumentApp.ParagraphHeading.TITLE);
// 4. Walk the sections in order, appending a heading and a bulleted
// list for each type that actually has entries.
for (const section of SECTIONS) {
const filtered = items.filter((i) => i.type === section.type);
if (filtered.length === 0) continue;
body.appendParagraph(section.heading)
.setHeading(DocumentApp.ParagraphHeading.HEADING2);
for (const f of filtered) {
body.appendListItem(f.description);
}
}
// 5. Save the Doc and hand back its URL.
doc.saveAndClose();
Logger.log(`Wrote release notes for ${version}: ${doc.getUrl()}`);
return doc.getUrl();
}
/**
* Reads the first sheet of a spreadsheet into an array of objects,
* keyed by the header row. Keeps the main function readable.
*/
function readSheet(id) {
const [h, ...rows] = SpreadsheetApp.openById(id)
.getSheets()[0]
.getDataRange()
.getValues();
return rows.map((r) => Object.fromEntries(h.map((k, i) => [k, r[i]])));
}
How it works
buildReleaseNotestakes a version string and callsreadSheetto load the whole changelog as an array of objects, then filters it down to rows whoseversionmatches.- If no rows match, it throws straight away — an empty release notes document is worse than a clear error.
- It creates a new Doc named after the version and adds a title paragraph.
- It walks the
SECTIONSlist in a fixed order. For each type, it filters the matching items, and — only if there are any — appends aHEADING2and one bulleted list item per change. Empty sections are skipped, so a fix-only release never shows an empty “Features” heading. - It saves the Doc and returns the URL so you can open or share it.
Example run
Say the Changelog sheet holds these rows:
| version | type | description |
|---|---|---|
| 2.4.0 | feat | Bulk export now supports CSV and XLSX |
| 2.4.0 | fix | Date picker no longer offsets by a day in some timezones |
| 2.4.0 | breaking | The legacy /v1/reports endpoint has been removed |
| 2.3.0 | fix | Login redirect loop on Safari |
Calling buildReleaseNotes('2.4.0') produces a Doc titled Release notes —
2.4.0 with three sections:
- Breaking changes — The legacy
/v1/reportsendpoint has been removed - Features — Bulk export now supports CSV and XLSX
- Fixes — Date picker no longer offsets by a day in some timezones
The 2.3.0 row is ignored, and because there is only one of each type, you get exactly three headings.
Run it
This runs once per release, so trigger it by hand:
- In the Apps Script editor, open a function that calls
buildReleaseNotes('2.4.0')with your version, or run it from the editor after setting the argument. - Approve the authorisation prompt the first time.
- Open the URL it logs to read the finished Doc.
To make it self-serve, add a custom menu that prompts for the version:
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('Release tools')
.addItem('Build release notes', 'promptForVersion')
.addToUi();
}
function promptForVersion() {
const ui = SpreadsheetApp.getUi();
const res = ui.prompt('Which version?', ui.ButtonSet.OK_CANCEL);
if (res.getSelectedButton() === ui.Button.OK) {
buildReleaseNotes(res.getResponseText().trim());
}
}
Watch out for
- The
typevalues must match exactly. A row taggedfeatureinstead offeatlands in no section and is silently dropped. Use data validation on the column to keep the three values clean. - Items appear in sheet order within each section. If you want the most important change at the top of “Features”, sort the changelog rows before running, or add a priority column and sort on it.
DocumentApp.createalways makes a fresh Doc in your My Drive root. If you want release notes filed in a specific folder, move the file withDriveAppafter creating it, or copy a template instead.- The Doc is plain — headings and bullets, no styling. If you publish to a
branded template, replace
DocumentApp.createwith amakeCopyof a styled template and append into that instead.
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