●2.5ONLY — From the September 18 changelog: access to the 2.5 models is now limited to people who have actively used them. They are not deprecated and the API keeps serving them, while new projects are pointed at 3.5 Flash-Lite or 3.8 Flash●3.8LIVE — Gemini 3.8 Live and 3.8 Live Extended Thinking went GA on September 15. Both are audio-to-audio models for the Live API, and the second keeps reasoning in the background during the call●09/30 — gemini-omni-flash-preview shuts down on September 30, seven days away. Its successor, gemini-omni-1.1-flash, went GA on August 27●ONCE — A report that "allow for this session" only lasts one call when the command contains a path has drawn 159 comments. The question underneath is how approvals should be scoped at all●NEW — When a function call comes back as plain text, suspect the property names in your tool declaration first●HISTORY — Writing just three things outside the tool — what you settled on, the instructions you used, and why you redid something — keeps past work from disappearing with a model●2.5ONLY — From the September 18 changelog: access to the 2.5 models is now limited to people who have actively used them. They are not deprecated and the API keeps serving them, while new projects are pointed at 3.5 Flash-Lite or 3.8 Flash●3.8LIVE — Gemini 3.8 Live and 3.8 Live Extended Thinking went GA on September 15. Both are audio-to-audio models for the Live API, and the second keeps reasoning in the background during the call●09/30 — gemini-omni-flash-preview shuts down on September 30, seven days away. Its successor, gemini-omni-1.1-flash, went GA on August 27●ONCE — A report that "allow for this session" only lasts one call when the command contains a path has drawn 159 comments. The question underneath is how approvals should be scoped at all●NEW — When a function call comes back as plain text, suspect the property names in your tool declaration first●HISTORY — Writing just three things outside the tool — what you settled on, the instructions you used, and why you redid something — keeps past work from disappearing with a model
Putting Gemini and Google Workspace to Work — Docs, Sheets, Drive, and an Apps Script That Actually Runs
Field notes on running Gemini inside Google Workspace as a daily process rather than a feature tour: where the side panel earns its keep, where you should drop down to the API, a working Apps Script that summarizes a Drive folder, and the checks that keep generated numbers out of your decisions.
Putting Gemini and Google Workspace to Work — Docs, Sheets, Drive, and an Apps Script That Actually Runs
At the start of every month I pull the AdMob revenue CSV into Sheets and try to work out where the change came from. If you ship apps on your own, this chore comes back around whether you enjoy it or not. For a long time it meant copying data out of Sheets, pasting it into a separate Gemini tab, and carrying the answer back into Docs. The round trip was tedious enough that I quietly started looking at the numbers less often.
What changed once Gemini moved inside Workspace is that the round trip disappeared. What did not disappear was the judgment. Answers from the side panel arrive in clean, confident prose, which makes them look correct. Push one of those straight into a monthly decision without checking it and you will be wrong quietly, which is the worst way to be wrong.
So instead of touring the features, this piece covers the three things I actually had to settle to run the integration day to day: how far the side panel takes you, where the API starts, and how to verify what comes back.
The side panel is strongest when it edits, not when it writes
The panel on the right side of Docs gets talked about as a drafting tool, but in practice the editing path is what earns its place. Select an existing paragraph, ask for it in a third of the words, and the body text is replaced in place. Because nothing has to be carried in from elsewhere, you can work at a much finer grain.
Working at a finer grain changes how you phrase instructions. Rather than composing one perfect brief for a whole document, it is faster to go paragraph by paragraph — lead with the conclusion here, add one concrete example there. The time spent crafting a single long instruction is usually more expensive than the small iterations it was meant to replace.
The cases the panel handles badly are equally clear. Anything you need to repeat dozens of times, anything whose results you want logged, and anything that has to run at a fixed hour without a human present. Hit one of those three and you are already in API territory.
Nature of the work
Better tool
How to tell
One-off rewriting or tightening
Side panel
You can eyeball the result and accept or reject it
Reading a trend out of a few rows
Side panel
Small enough to re-check the numbers yourself
The same operation 10+ times
API / Apps Script
Effort grows linearly with volume
Scheduled or unattended runs
Apps Script trigger
Nobody is there to press the button
Results you may need to audit later
API
Input, model, and output all need storing
I keep that table close by so the decision takes thirty seconds. Re-deriving the boundary every time becomes its own chore.
In Sheets, the question shapes the answer
The worst possible prompt against a Sheets selection is "analyse this data." What comes back is a paragraph that gently observes an average and a direction of travel.
There is only one useful fix: decide the shape of the answer before you ask. Against the monthly AdMob figures, the phrasing I settled on runs roughly like this.
"Revenue moved month over month. Determine whether that movement is explained by (a) impression volume, (b) eCPM, or (c) both, then list the two largest contributors in order. Quote the figures from the columns."
Once you supply the axis of decomposition, the reply stops being commentary and becomes a verdict. A verdict is something you can check. Commentary is not.
The other clause that pulls real weight is the demand for quotation. Adding "quote the figures from the columns" noticeably cuts the rate at which unsourced numbers slip in — and when one does slip in, walking back to the cited column exposes it immediately.
✦
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 concrete rule for deciding what stays in the side panel and what belongs in the API
✦A working Apps Script that reads a Drive folder and produces a sourced summary Doc
✦Two near-zero-cost checks that catch fabricated figures before they reach a business decision
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.
You can gather information across Drive folders from the side panel, but if it is a monthly job, automate it. Anything that depends on a human starting it will be skipped in a busy month.
The script below reads the text-bearing files in a folder, sends them to the Gemini API in a single call, and creates a summary Doc. Store the API key in Script Properties and it runs as written.
const MODEL = 'gemini-3-flash';const ENDPOINT = 'https://generativelanguage.googleapis.com/v1beta/models/' + MODEL + ':generateContent';function summarizeFolder(folderId, title) { const key = PropertiesService.getScriptProperties() .getProperty('GEMINI_API_KEY'); if (!key) throw new Error('GEMINI_API_KEY is not set'); const docs = collectText(folderId); if (docs.length === 0) throw new Error('No matching files in folder'); const brief = [ 'The following files belong to a single project.', 'Merge duplicated statements and organise under three headings:', '1. Decided 2. Open questions 3. Next actions', 'End every item with the source file name in parentheses.', ].join('\n'); const body = { contents: [{ role: 'user', parts: [{ text: brief + '\n\n' + docs.join('\n\n---\n\n') }], }], generationConfig: { temperature: 0.2 }, }; const res = UrlFetchApp.fetch(ENDPOINT, { method: 'post', contentType: 'application/json', headers: { 'x-goog-api-key': key }, payload: JSON.stringify(body), muteHttpExceptions: true, }); if (res.getResponseCode() !== 200) { throw new Error('API error: ' + res.getContentText().slice(0, 300)); } const parts = JSON.parse(res.getContentText()) .candidates[0].content.parts; const text = parts.map(function (p) { return p.text || ''; }).join(''); const doc = DocumentApp.create(title + ' summary ' + Utilities.formatDate(new Date(), 'Asia/Tokyo', 'yyyy-MM-dd')); doc.getBody().appendParagraph(text); return doc.getUrl();}function collectText(folderId) { const out = []; const files = DriveApp.getFolderById(folderId).getFiles(); while (files.hasNext()) { const f = files.next(); const mime = f.getMimeType(); if (mime === MimeType.GOOGLE_DOCS) { out.push('# ' + f.getName() + '\n' + DocumentApp.openById(f.getId()).getBody().getText()); } else if (mime === MimeType.PLAIN_TEXT) { out.push('# ' + f.getName() + '\n' + f.getBlob().getDataAsString()); } } return out;}
Three details in there are deliberate.
muteHttpExceptions: true lets the script inspect the status code itself. Without it, Apps Script throws before you can read the response body, and you end up re-running a call without ever seeing what the API objected to. This was the first thing that caught me out: I mistook a 400 for a 429, added backoff that could never help, and lost half a day narrowing it down.
The parts array is joined rather than indexed. Responses are not guaranteed to arrive as a single part, and parts[0].text will silently truncate the day that changes.
And the brief insists on source file names. A summary gets harder to verify precisely as it gets easier to read. Leaving the provenance inline means you can chase down the one line that looks wrong instead of re-reading everything.
One more thing that matters in real use: Apps Script executions are time-limited, so handing a large folder to this function straight will get you cut off mid-run. The fix is unglamorous — chunk what collectText() returns at around twenty files and run the folder in several passes. It finishes sooner than trying to do it all at once.
Checks before generated text touches a decision
If a summary is going to inform a real decision, it needs a floor of verification. Mine adds no extra API calls and comes in two passes.
The first pass reconciles numbers. Pull every figure out of the answer with a regular expression and confirm it exists in the source. One unmatched figure sends the whole summary to manual review.
The second pass checks structure: are all three headings present, and does every item carry its parenthetical source? In my experience, when that scaffolding breaks the substance has usually broken with it.
Both are a handful of lines, and the payoff was larger than I expected — the number of times I took a monthly summary purely on trust dropped sharply. The aim is not a complete quality judgment. It is stopping the obviously broken output. At the scale one person operates at, that trade feels right.
Three rules I settled on running this solo
Running several apps and blogs alone means the convenient features are exactly the ones that need rules, or they fall over.
First, the model ID lives in one constant in the Apps Script and nowhere else. Workspace will swap its internal models on its own schedule, but anything you wrote yourself is yours to keep current — and scattered IDs turn every swap into a search party.
Second, the API key goes in Script Properties, never in the code. Apps Script is easy to share, and whatever is in the source gets shared with it.
Third, every generated Doc carries its generation date in the title. Six months later, a summary with no date attached was worth about as much as no summary at all. For anything you may want to read chronologically later — App Store and Google Play review histories are the obvious case — that small habit pays for itself.
Where not to push it
Even with tight integration there are edges. Pulling live values from an external API is not something Workspace finishes on its own. Reproducing deeply nested conditional logic was never stable for me. And pointing this at a very large sheet in one go makes runtime unpredictable.
None of that is really "cannot." It is "another tool will be faster." Forcing everything into one environment adds steps rather than removing them.
One thing to try next
Pick a single Drive folder you find yourself revisiting every month and run summarizeFolder() against its ID once. Read the Doc it produces, then pull up two or three of the files named in parentheses and see whether the claims hold. If they do, that folder is safe to put on a trigger. If they do not, the fix starts with rewriting the axis of decomposition in the brief.
Whether something gets automated comes down to one question for me: can I verify what it produces? Not whether it saves time. I hope some of this proves useful in your own setup.
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.