Summarize pros and cons from reviews
Extract what Northwind customers love and hate about each product into a single roll-up.
Published Nov 3, 2025
Northwind’s product team reads reviews when a launch is fresh, then quietly stops a few weeks in. By the time the next planning cycle comes round, there are hundreds of comments per product and nobody has the appetite to read them end-to-end. The signal is in there — what people genuinely love, what they genuinely hate — but it never makes it onto a slide.
This script does the reading for you. It pulls every review for one product out of a sheet, hands the lot to Claude, and asks for two short lists back: a pros list and a cons list, in strict JSON. The output is the table you’d write by hand if you had a free afternoon — except this one took fifteen seconds and costs less than a sandwich.
What you’ll need
- A Google Sheet of reviews with three columns: date, product name, and the review text (the script reads columns A, B and C with a header row).
- An Anthropic API key saved as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - The product name you want summarised — passed in as the function argument.
The script
// The spreadsheet that holds your reviews.
const REVIEWS_SHEET_ID = '1abcReviewsId';
// Column indexes in the reviews sheet (zero-based after the header row).
const PRODUCT_COL = 1;
const REVIEW_COL = 2;
// How many reviews to include in one pass. Trims very long histories so the
// prompt stays inside a sensible token budget — see "Watch out for".
const MAX_REVIEWS = 200;
/**
* Pulls every review for a given Northwind product and asks Claude for a
* pros/cons summary as strict JSON.
*
* @param {string} product - The exact product name as it appears in column B.
* @returns {{pros: string[], cons: string[]}} The parsed summary.
*/
function summariseProsCons(product) {
if (!product) throw new Error('summariseProsCons needs a product name.');
// 1. Read the sheet, drop the header, keep only rows for this product.
const rows = SpreadsheetApp.openById(REVIEWS_SHEET_ID).getSheets()[0]
.getDataRange()
.getValues()
.slice(1)
.filter((r) => r[PRODUCT_COL] === product)
.map((r) => r[REVIEW_COL])
.filter(Boolean);
if (!rows.length) {
Logger.log('No reviews found for "' + product + '".');
return { pros: [], cons: [] };
}
// 2. Format the sample as a bulleted list — the shape Claude reads best.
const reviews = rows.slice(0, MAX_REVIEWS).join('\n- ');
// 3. Pin the output to a fixed JSON schema. No prose, no markdown.
const prompt =
'Summarise these Northwind reviews for "' + product + '". ' +
'Return ONLY a JSON object — no prose, no markdown — in this shape: ' +
'{"pros": [string], "cons": [string]}. ' +
'Give 3-5 entries per list, each a short noun phrase grounded in the ' +
'reviews (not invented).\n\n- ' + reviews;
// 4. Sonnet handles the synthesis; strip any code fence before parsing.
const reply = callClaude(prompt, 'claude-sonnet-4-6', 600);
return JSON.parse(stripFences(reply));
}
/**
* 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, model = 'claude-haiku-4-5-20251001', maxTokens = 400) {
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,
max_tokens: maxTokens,
messages: [{ role: 'user', content: prompt }],
}),
muteHttpExceptions: true,
});
return JSON.parse(res.getContentText()).content[0].text.trim();
}
How it works
summariseProsConstakes a product name and opens the reviews spreadsheet, reading the full data range as a 2D array.- It drops the header row, filters down to rows whose product column matches, and pulls the review text out of column C. Blank rows are removed so they don’t pad the prompt with empty bullets.
- If no reviews match, it logs a message and returns empty lists — no wasted API call.
- The first
MAX_REVIEWS(200) entries are joined into a bulleted list, the format Claude summarises most reliably. - The prompt pins the output to a strict JSON schema: an object with
prosandconsarrays. Asking for short noun phrases keeps the result skimmable and stops Claude writing paragraphs. stripFencesremoves any code fence Claude might wrap the JSON in, thenJSON.parseturns the reply into a real object the caller can use.
Example run
Say the reviews sheet has rows like these for Northwind Tote:
| Date | Product | Review |
|---|---|---|
| 2025-10-12 | Northwind Tote | Lovely fabric, holds my laptop fine, strap dug into my shoulder after an hour. |
| 2025-10-14 | Northwind Tote | Great colour and stitching — but the inner pocket is way too small. |
| 2025-10-16 | Northwind Tote | Sturdy and looks smart. Wish it had a zip on top. |
Calling summariseProsCons('Northwind Tote') returns:
{
"pros": ["Quality fabric and stitching", "Smart appearance", "Holds a laptop comfortably"],
"cons": ["Strap is uncomfortable for long carries", "Inner pocket is too small", "No top zip"]
}
That is the slide you needed — three things to keep, three things to fix — distilled from the whole review backlog in one call.
Run it
This is an on-demand job, not a scheduled one. Run it when you need a summary:
- In the Apps Script editor, open a function that calls
summariseProsConswith the product name you want, then click Run. - Approve the authorisation prompt the first time.
- Read the returned object in the logs, or wire it into a sheet, doc, or Slack message of your own.
For a per-product roll-up across the catalogue, loop over the unique product
names in column B and write the result to a Summaries tab — one row per
product, with the pros and cons joined into two cells.
Watch out for
- The summary is grounded in the reviews you send. If you only send 20 rows, expect 20 rows’ worth of insight — not a brand-wide truth.
MAX_REVIEWSis capped at 200 to keep the prompt cheap. For products with more reviews, raise the cap andmax_tokenstogether, or summarise in batches and ask Claude to merge the lists in a second pass.- Strict JSON keeps this reliable.
stripFenceshandles the common code-fence case; ifJSON.parsestill throws, log the raw reply and tighten the prompt rather than reaching for regex. - This script summarises one product at a time. If you want themes across the whole catalogue instead, use Classify customer feedback by theme.
Related
Summarize chat and Slack exports
Digest Northwind's long Slack conversations into recaps — for catch-up after PTO.
Updated Dec 5, 2025
Digest daily news into a personal briefing
Summarise headlines on a schedule — Northwind morning briefing for Awadesh.
Updated Nov 19, 2025
Summarize YouTube videos into notes
Turn transcripts into Northwind study summaries — one Doc per video.
Updated Oct 6, 2025
Build a competitor-mention monitor
Summarise what Northwind's rivals are doing each week — feeds in, summary out.
Updated Sep 12, 2025
Summarize a folder of PDFs into a briefing
Digest Northwind's research PDFs into one structured briefing Doc.
Updated Aug 27, 2025