Build a GENAI_ARRAY() function for whole columns
Process ranges in one efficient batched call — one prompt, many rows answered in parallel.
Published Jun 28, 2025
A per-cell AI formula is handy, but on a long column it is wasteful — twenty rows means twenty separate API calls, twenty round trips, and twenty chances to hit a rate limit. When Northwind needs the same instruction applied to a whole list, that is a lot of overhead for one job.
This builds =GENAI_ARRAY(), a custom function that takes a prompt and a
range. It sends the entire range to Claude as a single numbered list, gets one
numbered list back, and spills the answers down the column. One call, one
prompt, the whole column answered at once.
What you’ll need
- An Anthropic API key stored as
ANTHROPIC_API_KEYin Script Properties — see Store API keys and secrets securely. - A bound script — open the spreadsheet, then Extensions → Apps Script — so the function is available in that sheet.
- A column of values to process, with an empty column beside it for the result to spill into.
The function
// Claude model used for the batch. Haiku keeps a whole-column call
// fast and cheap.
const MODEL = 'claude-haiku-4-5-20251001';
// Token ceiling for the batched reply. Must be large enough to hold
// one answer per row — raise it for long columns or long answers.
const MAX_TOKENS = 4000;
/**
* Applies one prompt to an entire range in a single Claude call and
* spills the answers down a column.
* @param {string} prompt - The instruction to apply to every value.
* @param {Array<Array>} range - The cell range to process.
* @returns {Array<Array<string>>} One answer per input row.
* @customfunction
*/
function GENAI_ARRAY(prompt, range) {
// 1. Nothing selected — return blank rather than erroring.
if (!range) return '';
// 2. Flatten the 2-D range to a list and drop blank cells.
const flat = [].concat.apply([], range).filter(Boolean);
if (!flat.length) return '';
// 3. Number each value so answers can be matched back to rows.
const numbered = flat.map((v, i) => `${i + 1}. ${v}`).join('\n');
const full = `${prompt}\n\n` +
`Return one answer per line, prefixed with its number.\n\n${numbered}`;
// 4. Send the whole list to Claude in a single request.
const apiKey = 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': apiKey, 'anthropic-version': '2023-06-01' },
payload: JSON.stringify({
model: MODEL,
max_tokens: MAX_TOKENS,
messages: [{ role: 'user', content: full }],
}),
});
// 5. Split the reply into lines, strip the "1. " prefixes, and
// return a column (one answer per row) for Sheets to spill.
const text = JSON.parse(res.getContentText()).content[0].text;
return text.split('\n')
.map((l) => l.replace(/^\d+\.\s*/, '').trim())
.filter(Boolean)
.map((v) => [v]);
}
How it works
GENAI_ARRAYis marked@customfunction, so Sheets accepts it as a formula. Unlike a per-cell function, it takes a whole range as its second argument.- If the range is missing it returns blank, and if every cell is empty it does the same — no point billing for an empty list.
- It flattens the 2-D range into a flat list, drops blanks, then rebuilds it as a numbered list. The numbers are the trick that lets answers be lined back up with their source rows.
- It wraps the numbered list in the prompt, adding an instruction to return one numbered answer per line, and posts the lot to Claude in a single request.
- It splits the reply on newlines, strips each
1.prefix with a regex, drops any blank lines, and wraps each answer in its own array. - Returning an array of single-cell rows is what makes the result spill — Sheets fills the answers straight down the column from the formula cell.
Example run
Put values in column A and the formula in B2:
| A (input) | B (spilled result) |
|---|---|
| Quarterly strategy workshop | event |
| Annual support contract | service |
| Wireless desk lamp | product |
| Logo redesign package | service |
The formula =GENAI_ARRAY("Categorise each as 'product', 'service', or 'event':", A2:A5) sits in B2, fires one API call, and the four answers spill
down B2:B5 automatically.
Use it
Type the formula into the top cell of an empty column:
=GENAI_ARRAY("Categorise each as 'product', 'service', or 'event':", A2:A20)
It spills down the column — one API call processes the whole range.
Watch out for
- The reply must keep one answer per line, in order. If Claude merges two answers or adds a stray blank line, the spilled column drifts out of alignment with the source rows. Keep the prompt’s instruction explicit.
MAX_TOKENScaps the whole reply, not each row. A long column can be truncated mid-list — raiseMAX_TOKENS, or split the range and call the function twice.- Custom functions time out at 30 seconds. A single batched call is well within that for normal ranges, but a very large range with verbose answers can run long — batch it.
- The spilled range needs empty cells below the formula. If anything is in the
way, Sheets shows a
#REF!spill error until you clear it. - This call has no caching, unlike a per-cell
GENAI(). Every recalculation re-runs the whole batch, so reach for it on stable data, not a column you edit constantly.
Related
Build a GENAI() custom function for Sheets
Call Claude directly from a Northwind Sheet cell with =GENAI(prompt) — with caching baked in.
Updated Jun 24, 2025
Build retrieval-augmented Q&A over your data
Answer Northwind questions grounded in your own Sheet data — pass relevant rows as context.
Updated Feb 27, 2026
Parse semi-structured listings into tables
Extract recipes, specs, or ads from Northwind content into clean spreadsheet rows.
Updated Mar 7, 2026
Generate and test email subject lines
A/B test AI-written Northwind subject lines for open rate — outputs ranked by past performance.
Updated Mar 3, 2026
Build an AI weekly-report narrator
Turn Northwind metrics into a written executive summary — numbers in, prose out.
Updated Feb 23, 2026