GEMINI LABJP
FLASH35 — Gemini 3.5 Flash is GA and now powers gemini-flash-latest, delivering sustained frontier performance on agentic and coding tasksAGENTS — Managed Agents launch in public preview in the Gemini API, running stateful autonomous agents in isolated Google-hosted Linux sandboxesANTIGRAV — The general-purpose managed agent antigravity-preview-05-2026 enters public preview: it plans, reasons, runs code, manages files, and browses the webTTS — Streaming speech generation is now supported for gemini-3.1-flash-tts-preview via streamGenerateContentNANO — Nano Banana 2 Lite arrives as the fastest, most cost-efficient Gemini Image modelDEPRECATE — Legacy image generation models are deprecated and shut down on August 17, 2026; plan your migration earlyFLASH35 — Gemini 3.5 Flash is GA and now powers gemini-flash-latest, delivering sustained frontier performance on agentic and coding tasksAGENTS — Managed Agents launch in public preview in the Gemini API, running stateful autonomous agents in isolated Google-hosted Linux sandboxesANTIGRAV — The general-purpose managed agent antigravity-preview-05-2026 enters public preview: it plans, reasons, runs code, manages files, and browses the webTTS — Streaming speech generation is now supported for gemini-3.1-flash-tts-preview via streamGenerateContentNANO — Nano Banana 2 Lite arrives as the fastest, most cost-efficient Gemini Image modelDEPRECATE — Legacy image generation models are deprecated and shut down on August 17, 2026; plan your migration early
Articles/Workspace
Workspace/2026-07-15Advanced

The Table Was There, but the Rows and Columns Weren't — Preserving Docs Structure Before It Reaches Gemini

getBody().getText() flattens Google Docs tables into a column of loose values. Here is what that cost me on a 42-row ledger, the Apps Script extraction layer that keeps the structure, and the acceptance test that keeps it honest.

Apps Script7Google Docs3Gemini API185Structured ExtractionWorkspace Automation

Premium Article

Every number was correct. The answer still made no sense.

I keep a ledger in Google Docs for the four sites I run as an indie developer — articles published, tasks left, deadlines. An Apps Script job hands it to Gemini and asks which July deadlines haven't been started yet. Every date and count that came back existed somewhere in that ledger. They were just attached to the wrong rows.

Gemini wasn't the problem. One line of my own code was.

The values survive; the rows don't

Here's what I had written:

function askAboutLedger() {
  const text = DocumentApp.openById(DOC_ID).getBody().getText();
  const prompt =
    'From the operations ledger below, list every item with a July deadline, tagged by site, as a JSON array.\n\n' + text;
  return callGemini(prompt);
}

getText() hands you the whole body as one string. It's convenient, and for a meeting note or a plain draft made of paragraphs, it's genuinely enough.

Tables are where it stops being enough. Logging the actual payload made the failure obvious:

Site
Published in July
Open tasks
Deadline
Owner
Claude Lab
12
3
2026-07-20
Me
Gemini Lab
14
1
2026-08-17
Me

The cells are on the floor. The container is gone. Asking a model to find "Gemini Lab's deadline" in that is asking it to count in fives and rebuild the grid from memory. With a fixed column count it can sometimes manage. Insert one empty cell and every alignment after it shifts.

Gemini wasn't hallucinating. It was tidying up spilled values into something plausible.

What getText() throws away

DocumentApp holds the body as a tree of elements. getText() walks that tree depth-first and concatenates the text — which means everything that isn't text is discarded.

In the DocAfter getText()Effect on extraction
Table cell boundaries and positionsNewline-separated plain textRow/column mapping is gone (the worst one)
Heading levels (H1–H6)Ordinary paragraphsSection boundaries disappear
List nesting and numberingA flat sequence of linesParent/child relations are lost
Link targetsDisplay text onlyReferences can't be followed
FootnotesAbsent from the bodyCaveats vanish entirely
CommentsAbsent from the bodyDocumentApp can't read them at all

That last row is different in kind. Comments live outside DocumentApp — you need Drive API's comments.list to reach them. If a caveat you're sure you wrote never seems to reach the model, it's worth checking whether it was left as a comment rather than typed into the body.

I lost real time to the footnote case. An exception condition I'd written as a footnote never showed up in any extraction, and I spent an afternoon rewriting prompts. The prompt was fine. The text simply wasn't being sent.

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 measured comparison on a 42x5 ledger where row/column mix-ups dropped from 11 of 30 answers to 0
Complete Apps Script code that walks the Docs element tree and preserves headings, tables, and lists
An acceptance test on four structural invariants that catches silent decay when the Doc gets edited
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-04-09
Google Workspace × Gemini API Automation: Production Notes on 12 Apps Script Patterns
12 Gemini API + Apps Script patterns for Gmail, Docs, Sheets, and Calendar automation—plus the production snags I hit running this across four sites and an app support inbox: swallowed 429s, JSON code fences, the 6-minute cap, and flash-vs-pro routing, with measured numbers.
Workspace2026-07-13
Retiring the Poll That Waits on an Overnight Batch — An Apps Script doPost Sink for Gemini Signals
Polling a Gemini batch or long-running operation every five minutes from an Apps Script time trigger quietly stacks up UrlFetch calls and latency. Receive the webhook in doPost, treat it as an unverified signal, then confirm authoritatively and apply idempotently.
Workspace2026-07-08
Your Apps Script Gemini Automation Fails Every Month-End — Budgeting Against the UrlFetch Daily Quota
Apps Script automations that call Gemini stall on the UrlFetch daily call quota — a separate ceiling from the 6-minute limit and trigger counts. Here is a daily budget governor with backlog carry-over that keeps the job running on busy days, with working code and a verified simulation.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →