◉GEMINI LABJP
●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 7 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…●ARGON — gemini-4-argon is generally available as of 10/13, aimed at complex coding, agents, math and science●DEEP RES — deep-research-pro-preview-12-2025 shuts down 10/23, 7 days left. Move to deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 is GA (10/06). gemini-3.1-flash-image is deprecated with no shutdown date yet●MCP — One write-up cut an 85,000-token tool result to 310 tokens by wrapping it in an ADK FunctionTool●FLASH — gemini-3.7-flash and 3.5-flash now route automatically to newer Flash models (10/08)●NEW — Stop Re-Reading Every Crash Report: Gre…
Articles/Workspace
◧ Workspace/2026-07-16Advanced

I Put =GEMINI() in 420 Cells. The Calls Kept Coming on Days I Never Opened the Sheet

A custom function calling Gemini from Sheets looks best the moment it starts working, and it does not grow from there. Here is what the 30-second ceiling, uncontrolled recalculation, and read-only cells actually cost, measured, plus the code to move to a write-back design.

gemini-workspace5apps-script10google-sheetscost-controlindie-development5

✦ Premium Article

The cell note just said Exceeded maximum execution time (line 0). Thirty-seven cells out of 420 showed #ERROR!, scattered with no pattern I could see. Same prompt, same model, inputs of roughly the same length. Nothing was wrong with those particular rows. What was wrong was the arrangement: 420 formulas sitting next to each other in a column.

I had been collecting app review text in a spreadsheet — column C for the review, column D for =GEMINI_SUMMARY(C2). As an indie developer running this kind of thing solo, the appeal is obvious: no deployment, no backend, and the answer appears in the cell while you watch.

Ten rows worked beautifully. Dragging the formula down to 420 rows surfaced everything this shape had been hiding.

The #ERROR! cells were not the real problem. At the end of that week I looked at the usage numbers: I had written 420 formulas, and the script had made 1,180 calls. The count went up on mornings when nobody had touched the file.

Every cell is its own separate call

The optimization section of the official documentation states the core of it plainly: each time a custom function is used in a spreadsheet, Sheets makes a separate call to the Apps Script server. Dozens, hundreds, or thousands of custom function calls in one sheet make that process slow.

So pasting =GEMINI_SUMMARY(C2) into 420 cells is not one job processing 420 rows. It is 420 independent executions embedded in a spreadsheet, each with its own 30-second budget.

The difference shows up when things fail, and when things amplify. A 420-row batch can hold "how far did we get" in one place. Four hundred and twenty independent executions have no concept of progress at all. There are only cells that happen to contain a value and cells that happen to say #ERROR!.

Google publishes a custom function sample that fact-checks statements using an ADK agent and a Gemini model, and it is a good on-ramp. The trouble starts when you try to extend the on-ramp into a road.

The calls that arrive while you sleep

Recalculation does not happen when you decide it should. The documentation is specific: to trigger recalculation you must pass a referenced cell or range directly as an argument; otherwise the function will not recalculate until you edit the formula or change a referenced cell's value.

Read that the other way around. As long as you pass a referenced cell directly, every change to that reference triggers a fresh call. In my sheet, touching column C re-ran column D. Fixing a typo. Inserting a language column. Moving a column to sort. Every one of those is an edit.

There is a related constraint worth knowing: arguments must be deterministic. Volatile built-ins like NOW() or RAND() cannot be passed to a custom function — try it and the cell shows Loading... forever. The natural workaround is to park a fixed timestamp in a cell instead, and the moment you refresh that timestamp, you have pulled the recalculation trigger yourself.

Here is my week, counted. These are my numbers on my sheet, and they will move depending on how you work. Treat them as a shape to re-measure, not a benchmark.

TriggerTimesRows recalculatedCalls
Initial fill across 420 rows1420420
Typo fixes in column C (one cell at a time)232323
Inserting and removing a language column2420654*
Saving prompt tweaks in the script4—83
Total——1,180

* Not every row re-runs every time; some keep their cached value. That inconsistency is exactly what makes this hard to see — the sheet never tells you how many times it called.

Formulas written: 420. Actual calls: 1,180. A 2.8x multiplier. What matters is not the size of the multiplier but that it is disconnected from anything I chose to do. Inserting one column — an action with no apparent cost — quietly billed a few hundred requests.

To count it on your own sheet, one line on the calling side is enough.

/**
 * Summarizes review text (instrumented — this shape is not for production).
 *
 * @param {string} text The review body to summarize.
 * @return {string} The summary text.
 * @customfunction
 */
function GEMINI_SUMMARY(text) {
  // Record the fact of the call first. Count them in the Apps Script executions view.
  console.log('GEMINI_SUMMARY invoked len=%s', String(text).length);
 
  const key = PropertiesService.getScriptProperties().getProperty('GEMINI_API_KEY');
  const url = 'https://generativelanguage.googleapis.com/v1beta/models/'
    + 'gemini-flash-latest:generateContent?key=' + key;
 
  const res = UrlFetchApp.fetch(url, {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify({
      contents: [{ parts: [{ text: 'Summarize this review in one sentence.\n\n' + text }] }],
    }),
    muteHttpExceptions: true,
  });
 
  if (res.getResponseCode() !== 200) {
    // A custom function can only return a value. The cause dies here.
    return '#GEMINI_ERROR';
  }
  const json = JSON.parse(res.getContentText());
  return json.candidates[0].content.parts[0].text.trim();
}

Paste it, use the sheet normally for a week, then compare the execution count in the Apps Script dashboard against the number of formulas in the sheet. For me, that gap settled the argument faster than any reasoning about architecture did.

✦

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
✦A measurement recipe for counting the recalculation amplification your sheet generates on its own, plus the breakdown behind a measured 2.8x on 420 rows
✦Why the 30-second custom function ceiling is a different limit from the 6-minute one, and how to turn your per-row latency into a hard cap on rows per call
✦Complete menu-driven batch code for moving from formulas-in-cells to a status column with written-back values, and a decision table for when a custom function is still the right shape
Secure payment via Stripe · Cancel anytime
✦

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Gemini Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $10 for lifetime access
View Membership →

Related Articles

◧ Workspace2026-10-15
Before you turn on Meet's automatic notes: three things to settle in the calendar event
Some Meet note-taking settings can't be changed once someone has joined the call. Here is what to decide in the calendar event first — language, sharing scope, how it starts — and how to tell participants, following Google's help pages.
◧ Workspace2026-10-15
Keeping a Gemini Answer as a Dated PDF — Export to Docs, Then Freeze It with Apps Script
Gemini's Share & export menu has no PDF option. Here is how to export to Docs, freeze a dated PDF next to it with Apps Script, and avoid the getAs() file-naming trap.
◧ Workspace2026-10-03
Estimating a Sheets-Driven Gemini Bill Across the Year-End Price Switch with Apps Script
If you call the Gemini API from a spreadsheet, log the token counts per call and estimate the month in two columns: the intro price that ends on December 31, 2026 and the standard price from January 1, 2027. Apps Script, with real output.