appscript.dev
Automation Intermediate Sheets

Build an AI proofreading and style checker

Flag grammar and tone issues in a column of Northwind copy — every row gets feedback.

Published Oct 30, 2025

Northwind writes a lot of short copy — product blurbs, email snippets, social captions — and most of it lands in a spreadsheet before it lands anywhere else. A second pair of eyes would catch the typos and the off-brand phrasing, but a second pair of eyes is rarely free at the moment the copy is due.

This script gives every row that second read. It walks a column of draft copy, asks Claude to check each entry for grammar and for Northwind’s house tone — friendly, direct, no hype — and writes a one-line verdict into a feedback column. Most rows come back “OK”; the ones that do not get a specific note you can act on in seconds.

What you’ll need

  • A Google Sheet with your draft copy in a column headed text and an empty column headed feedback for the results.
  • An Anthropic API key saved as ANTHROPIC_API_KEY in Script Properties — see Store API keys and secrets securely.

The script

// The spreadsheet that holds the draft copy.
const COPY_SHEET_ID = '1abcCopyId';

// Northwind's house style — kept in one place so the prompt stays
// consistent and is easy to tweak.
const BRAND_TONE = 'friendly, direct, no hype';

/**
 * Walks the copy column and writes a one-line proofreading verdict
 * into the feedback column for every unchecked row.
 */
function proofreadColumn() {
  const sheet = SpreadsheetApp.openById(COPY_SHEET_ID).getSheets()[0];

  // 1. Read the whole sheet and split off the header row.
  const [h, ...rows] = sheet.getDataRange().getValues();

  if (!rows.length) {
    Logger.log('No copy to proofread — nothing to do.');
    return;
  }

  // 2. Map header names to column indexes so the code is not
  //    tied to a fixed column order.
  const col = Object.fromEntries(h.map((k, i) => [k, i]));

  // 3. Check each row: skip ones already done or with no text.
  rows.forEach((r, i) => {
    if (r[col.feedback] || !r[col.text]) return;

    // 4. Ask Claude for a single line of feedback, or "OK".
    const prompt =
      'Proofread for grammar and Northwind brand tone (' + BRAND_TONE + '). ' +
      'Return one line of feedback, or exactly "OK" if there is nothing to fix.' +
      '\n\n' + r[col.text];

    sheet.getRange(i + 2, col.feedback + 1).setValue(callClaude(prompt));
  });
  Logger.log('Proofreading pass complete.');
}

/**
 * Minimal Anthropic API call. Haiku is plenty for short copy checks,
 * and the key lives in Script Properties — never in 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-haiku-4-5-20251001',
      max_tokens: 100,
      messages: [{ role: 'user', content: prompt }],
    }),
    muteHttpExceptions: true,
  });
  return JSON.parse(res.getContentText()).content[0].text.trim();
}

How it works

  1. proofreadColumn opens the copy spreadsheet and reads the full data range, splitting the header row from the data.
  2. If there are no data rows, it logs a message and stops.
  3. It builds a name-to-index map from the header so the script keeps working even if columns are reordered.
  4. It walks each row, skipping any that already have feedback or have no text — so a re-run only processes new copy and never burns API calls twice.
  5. For each remaining row it builds a prompt carrying the house tone from BRAND_TONE, and asks for a single line of feedback or "OK".
  6. It calls Claude Haiku — fast and cheap, which matters when you are scoring one row at a time — and writes the verdict straight into the feedback cell.

Example run

Say the text column holds these drafts:

textfeedback
Our brand new platform revolutionises how you work!
The booking form takes about two minutes to fill in.
We was unable to process you’re request.

After a run, the feedback column fills in:

textfeedback
Our brand new platform revolutionises how you work!Drop “revolutionises” — too much hype for Northwind’s tone.
The booking form takes about two minutes to fill in.OK
We was unable to process you’re request.Grammar: “We were unable to process your request.”

The “OK” rows you skip; the two flagged rows take a few seconds each to fix.

Run it

This is an on-demand job — run it whenever a batch of copy is ready:

  1. In the Apps Script editor, select proofreadColumn and click Run.
  2. Approve the authorisation prompt the first time.
  3. Read down the feedback column and act on anything that is not “OK”.

To let writers run it themselves, add a custom menu to the sheet:

function onOpen() {
  SpreadsheetApp.getUi()
    .createMenu('Copy tools')
    .addItem('Proofread column', 'proofreadColumn')
    .addToUi();
}

Watch out for

  • The verdict is one line, by design. It flags the issue but does not rewrite the copy — a human still makes the edit, which keeps the writer in control.
  • Already-filled feedback cells are skipped. If you edit a row’s text and want a fresh check, clear its feedback cell first.
  • Tone is subjective. Claude follows BRAND_TONE, but if its calls feel off, give it a worked example of good and bad Northwind copy in the prompt.
  • A very large sheet means a long run. Apps Script caps execution at roughly six minutes, so for hundreds of rows process them in batches.

Related