Turn meeting notes into assigned action items
Extract tasks and owners from Northwind meeting transcripts into the Tasks sheet.
Published Aug 7, 2025
Every Northwind meeting ends the same way: someone says “I’ll write that up” and the action items sink into a wall of notes nobody opens again. The decisions are in the document — they are just buried between the discussion and the digressions, with no owner attached and no due date.
This script reads a meeting-notes doc, asks Claude to pull out only the
concrete tasks and who owns each one, and appends them to a shared Tasks
sheet with a status and a timestamp. Run it the moment a meeting wraps and the
follow-ups are tracked before anyone has left the room.
What you’ll need
- A Google Doc of meeting notes — the script takes the document ID as an argument, so you can point it at whichever doc you just finished.
- A Google Sheet to collect the tasks, with a header row of
Task,Owner,Status,Logged. - An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely.
The script
// The spreadsheet that collects extracted tasks.
const TASKS_SHEET_ID = '1abcTasksId';
// Status applied to every freshly extracted task.
const DEFAULT_STATUS = 'open';
/**
* Reads a meeting-notes doc, asks Claude to extract action items with
* owners, and appends each one to the Tasks sheet.
* @param {string} notesDocId The ID of the Google Doc holding the notes.
*/
function extractActionItems(notesDocId) {
// 1. Pull the full plain text of the meeting-notes document.
const text = DocumentApp.openById(notesDocId).getBody().getText();
if (!text.trim()) {
Logger.log('The notes document is empty — nothing to extract.');
return;
}
// 2. Ask Claude for strict JSON: a task and an owner per item.
const prompt =
'Extract action items from these Northwind meeting notes. ' +
'Return ONLY a JSON array — no prose, no markdown — in this shape: ' +
'[{"task": string, "owner": string}]. ' +
'Use "unassigned" as the owner when the notes name no one.\n\n' + text;
// 3. Parse the reply into real objects.
const items = JSON.parse(stripFences(callClaude(prompt)));
if (!items.length) {
Logger.log('No action items found in the notes.');
return;
}
// 4. Append one row per task with a status and a timestamp.
const sheet = SpreadsheetApp.openById(TASKS_SHEET_ID).getSheets()[0];
for (const it of items) {
sheet.appendRow([it.task, it.owner, DEFAULT_STATUS, new Date()]);
}
Logger.log('Logged ' + items.length + ' action items to the Tasks sheet.');
}
/**
* Claude occasionally wraps JSON in a ```json code fence. Strip it so
* JSON.parse never chokes on the markdown.
*/
function stripFences(text) {
return text.replace(/```(?:json)?/g, '').trim();
}
/**
* Minimal Anthropic API call. The key lives in Script Properties — it
* is never pasted into the code.
*/
function callClaude(prompt) {
const key = PropertiesService.getScriptProperties()
.getProperty('ANTHROPIC_API_KEY');
const res = UrlFetchApp.fetch('https://api.anthropic.com/v1/messages', {
method: 'post',
contentType: 'application/json',
headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: 'claude-sonnet-4-6',
max_tokens: 1000,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
extractActionItemsopens the meeting-notes document by ID and reads its body as plain text.- If the document is empty, it logs a message and stops — no wasted API call.
- It builds a prompt that pins the output to a strict JSON schema — an array
of objects, each with a
taskand anowner— and tells Claude to fall back to"unassigned"when no name is mentioned. - It calls Claude Sonnet, which handles the reasoning of separating real
commitments from general discussion.
stripFencesremoves any code fence, thenJSON.parseturns the reply into objects. - If nothing was extracted, it stops there.
- It appends one row per task to the
Taskssheet, tagging each with the default status and the current date so follow-ups have a paper trail.
Example run
Say the meeting-notes doc contains a passage like this:
Sandra agreed to send the revised quote to the Harlow client by Friday. We also need updated render thumbnails — Tom will handle that. General agreement that the booking form is too long, but we left that for later.
After a run, the Tasks sheet gains two rows (the booking-form remark is
discussion, not a commitment, so it is left out):
| Task | Owner | Status | Logged |
|---|---|---|---|
| Send the revised quote to the Harlow client by Friday | Sandra | open | 2026-05-25 |
| Produce updated render thumbnails | Tom | open | 2026-05-25 |
Run it
This is a once-per-meeting job, so run it by hand when the notes are ready:
- In the Apps Script editor, open
extractActionItemsand pass it the doc ID — either edit a wrapper function or call it from the editor with the ID. - Approve the authorisation prompt the first time.
- Open the
Taskssheet to see the new rows.
To trigger it straight from a doc, add a custom menu so the meeting owner can run it without touching the editor:
function onOpen() {
DocumentApp.getUi()
.createMenu('Meeting tools')
.addItem('Extract action items', 'extractFromThisDoc')
.addToUi();
}
function extractFromThisDoc() {
extractActionItems(DocumentApp.getActiveDocument().getId());
}
Watch out for
- Claude decides what counts as an action item. A vague “we should look into that” may or may not become a task — review the rows after the first few runs and tighten the prompt if it is too eager or too cautious.
- Owners are matched on names in the notes. If the doc says “Tom” but your team tracker uses full names, normalise the owner column before assigning.
- Running the same doc twice appends duplicate rows. Extract once per meeting, or add a check against an already-logged doc ID if re-runs are likely.
- Long transcripts can exceed the token budget. For hour-long meetings, raise
max_tokensor split the notes into sections and extract each in turn.
Related
Parse semi-structured listings into tables
Extract recipes, specs, or ads from Northwind content into clean spreadsheet rows.
Updated Mar 7, 2026
Extract follow-ups from call transcripts
Turn Northwind sales calls into actionable tasks — owner + task + due date per extracted item.
Updated Jan 10, 2026
Build an AI data-enrichment pipeline
Fill missing company and contact fields on Northwind's prospect list — Claude infers from the row.
Updated Nov 23, 2025
Build an AI invoice and receipt parser
Read Northwind documents into structured ledger rows — vendor, amount, line items.
Updated Oct 2, 2025
Extract entities and relationships from text
Build a structured graph from Northwind prose — people, companies, and how they connect.
Updated Sep 24, 2025