BlogEngineering

How to connect MongoDB to Google Sheets when documents are not rows

Nested documents, arrays and optional fields do not fit a sheet. Connect MongoDB to Google Sheets by choosing columns and flattening first.

Osman RamadanOsman Ramadan11 min read

Summarize with AI

How to connect MongoDB to Google Sheets when documents are not rows
On this page

To connect MongoDB to Google Sheets, decide the sheet's columns first, then flatten the documents into those columns in an aggregation pipeline before writing to the sheet. A spreadsheet is a rectangle and a MongoDB collection is not. Documents nest, arrays hold several values in one field, and two documents in the same collection can have different fields.

Something has to decide what the columns are. Deciding deliberately in an aggregation pipeline produces a sheet people can use. Letting an export tool guess produces a sheet with hundreds of columns, most of them empty.

What we'll cover

Key takeaways

  • Write down the sheet's columns before looking at the documents, and project exactly those fields in the aggregation pipeline.
  • Choose between one row per document and one row per array element, and state the choice in the sheet to prevent double-counting.
  • A missing field is not zero, so decide per field what "absent" means and report the share of missing values.
  • Write ObjectIds and large integers as text, because Google Sheets stores numbers as floating point and loses precision.
  • A recurring export that other processes read is a sign the data belongs in a warehouse, not a sheet.

Documents are not rows

Documents are not rows because MongoDB documents can nest, hold arrays, omit fields and vary in type. A spreadsheet row can do none of these.

Nesting has no spreadsheet equivalent. A sub-document becomes either several columns with dotted names or a cell containing unreadable text, and neither happens automatically.

Arrays hold several values in one field, and a spreadsheet cell holds one. Something must join them, take the first, count them, or produce one row per element.

Fields are optional by design. A column exists for some documents and not others, and the export must decide what the missing values become.

The same field can hold different types across documents, which a spreadsheet column cannot represent without picking one.

Document order is not row order. Without an explicit sort, two exports of unchanged data can produce different orders, which makes comparing them by eye impossible.

Each of these is a decision. An export that does not make them explicitly makes them implicitly, and usually wrongly.

Decide the columns first

Decide the columns before you look at the documents. This habit turns a recurring problem into a solved one.

Write down the columns the sheet needs. A dozen columns is usual, and fewer is common.

Project exactly those columns in the pipeline, so the sheet's shape is a decision, not an accident.

Name columns for the reader, not the schema. customer_email beats contact.primary.addr, because the reader does not know or care about the document structure.

Fix the column order. A sheet whose columns move between runs breaks every formula pointing at it.

Add a column deliberately when someone needs one, instead of exporting everything in case. Exporting everything is what produces the three-hundred-column sheet.

Building it with CodeWords

CodeWords connects to 3,000+ integrations (CodeWords integrations page, 2026), and each connection is made once and reused.

  1. Open CodeWords and start a new automation.
  2. Describe what should happen in plain language to Cody, the automation builder: which collection, which documents, which fields, and which sheet.
  3. Authorize the connection with a connection string that points at a secondary where possible, using a user scoped to the specific database, plus access to the target spreadsheet.
  4. Describe the exceptions: a document missing an expected field, a field of an unexpected type, and a result larger than the sheet can hold.
  5. Run it against a restored snapshot and check the columns before pointing anything at production.

You describe the outcome; Cody builds it, connects it, and deploys it. The Free plan costs $0, Pro costs $39 per month and Business costs $100 per month (CodeWords pricing page, 2026). Details are on the pricing page.

Flattening in the pipeline

Flatten documents in the database, in the aggregation pipeline, instead of after the documents arrive.

Project nested values to top-level names, so the pipeline emits documents that are already flat and already named for the reader.

Match first and early, so the pipeline can use an index instead of scanning the collection.

Compute derived values in the pipeline, such as a total, a count or a days-since figure. A spreadsheet formula will eventually be broken by someone.

Sort explicitly, always, so consecutive exports are comparable.

Limit deliberately, so a growing collection does not eventually produce a sheet that fails to write.

Keep the pipeline somewhere people can read it. Aggregation pipelines become dense quickly, and a pipeline buried inside an automation is untrustworthy the moment someone questions a number.

How to handle arrays

Handle arrays by choosing one row per document or one row per array element. This decision has the biggest effect on what the sheet means.

ChoiceEffectBest for
One row per documentKeeps the sheet countable: a hundred rows is a hundred orders. The array collapses into one cell or a count.Reports
One row per array elementMakes the detail available but breaks counting, because the same order appears several times and a summed column double-counts.Sheets that feed another process needing the detail

State which you chose in the sheet. The reader cannot tell by looking and will assume the choice that suits their question.

Never expand silently. A sheet with four times the rows someone expected is the fastest route to a wrong total in a meeting.

Fields only half the collection has

Schema drift arrives in a spreadsheet as empty cells, and empty cells mislead.

A missing field is not zero. A spreadsheet formula that averages the column treats it as zero.

Decide per field what absent means: blank, zero, or a marker such as "n/a". Then be consistent.

Report the share of values missing. If a column is empty for a third of rows, that fact matters more than the column's average, and it belongs in the sheet.

Sample the whole history, not recent documents, when deciding the columns. The drift lives in the old documents.

Expect old documents to use old field names. A column that looks sparse may be two columns that should be merged.

Types that confuse a spreadsheet

Five MongoDB types behave badly in Google Sheets.

ObjectIds are not numbers. Write them as text, or the spreadsheet will mangle them.

Dates arrive in UTC and land in the spreadsheet's own timezone, which is a workbook setting nobody has checked.

Large integers lose precision when Sheets stores them as floating point, so identifiers and large counts belong in text columns.

Decimal values round, which matters for anything financial.

Booleans become TRUE and FALSE. This works until someone tries to sum them.

When the spreadsheet should not be the destination

A sheet should not be the destination when a recurring export signals that someone needs more than a sheet provides.

People pivot it every week. They want a reporting tool, and the sheet is the workaround.

Another process reads the sheet. The sheet is now infrastructure with no tests, no schema enforcement, and an owner who can reorder the columns by accident.

The row count keeps growing. The destination will eventually fail, and a warehouse table with a proper tool on top is the answer.

Several sheets pull overlapping data with their own filters. The definitions have already diverged and someone reconciles them by hand.

The data contains personal data. A spreadsheet is a copy outside your retention and access controls, and nothing will clean it up.

A sheet suits a small, stable answer that people read and occasionally annotate. When it does more than that, say so instead of making the export more elaborate.

How to make the export reliable

A reliable export reads from a secondary, projects only needed fields and shows its own run details.

Read from a secondary where you have one, so a report does not compete with the application.

Project only the fields you need. This is faster and moves less data over the network.

Replace a named range instead of appending, unless the sheet is deliberately a log.

Write the run time and the row count into the sheet, visibly.

Report the outcome: documents read, rows written, and documents skipped with the reason. The skipped count is where drift becomes visible.

Limits of the export

The export has five limits.

Sheets has a total cell limit, and an expanded array export reaches it quickly.

Sheets API writes are rate limited, so large writes need chunking.

Aggregation stages have memory limits, and exceeding them fails unless disk use is allowed. Filtering earlier resolves this better than raising limits.

Very large result sets should be paged, not materialised in one go.

A secondary lags slightly behind the primary. This does not matter for a report, but it matters if you check something just written.

What to set up first

Set up one report first, with the columns written down before the pipeline is built, an explicit sort, a replaced named range, and the run time and row count in the sheet. Pick the export someone currently does by hand, because it already has a definition and an audience.

Two habits make the difference. Decide the columns before you look at the documents, because letting the export discover them produces unusable sheets. And state in the sheet whether a row is a document or an array element, because that ambiguity produces more wrong totals than anything else here.

Frequently asked questions

Why does my export have hundreds of columns?

The export inferred the columns from the documents instead of being told. Project exactly the fields you want in the aggregation pipeline, which is usually about a dozen, and name each one for the reader. A fixed projection also keeps the column order stable between runs.

How should arrays be handled?

Choose between one row per document, with the array collapsed or counted, and one row per element. Both are valid. Expanding breaks counting, because one order appears several times, so state your choice in the sheet to stop readers double-counting.

Why are some cells empty?

Those documents do not have that field, which is normal in MongoDB. Decide what absent means for each field, apply the rule consistently, and report the share of missing values. An average over a column that is a third empty misleads readers.

Why do identifiers look wrong in the sheet?

ObjectIds and large integers both suffer, because Google Sheets stores numbers as floating point and loses precision on long values. Write identifiers as text instead of numbers, and the values survive the export exactly as they appear in MongoDB. Convert identifiers to text before writing, and keep that conversion in one place.

Should the export read from the primary?

Read from a secondary where you have one, so a report does not compete with application traffic. The slight replication lag does not matter for reporting. It only matters if you are checking a record that was written moments before the export ran.

Why do two exports of the same data look different?

The pipeline has no explicit sort. MongoDB does not guarantee document order, so add a deterministic sort to the pipeline. Without one, comparing two exports by eye is impossible, even when the underlying data has not changed. Fix the field order and the formats in the export, so repeated runs match exactly.

Where should the flattening happen?

Flatten in the aggregation pipeline, inside the database. This is faster, moves less data, and makes the sheet's shape an explicit decision stored where people can read it, instead of a result that emerged from whatever the export tool guessed.

When is a spreadsheet the wrong destination?

A spreadsheet is the wrong destination when people pivot it weekly, when another process reads it, when the row count keeps growing, or when several sheets pull overlapping data with their own filters. In each case the sheet has become infrastructure without a schema or tests.

Is it a problem if another system reads the sheet?

Yes, because anyone can reorder columns, insert a row or rename a tab, and nothing enforces the shape the consumer expects. If a process depends on the data, store it somewhere with a schema, such as a warehouse table, and let the sheet read from there.

How do I find the fields worth exporting?

Sample across the whole history, not only recent documents, and report which fields appear in what share of documents. The drift lives in the old documents, and a column that looks sparse is often two fields that were renamed at some point.

Get started today

Your first workflow is free to build.

Describe what you need. Cody handles the build, the connections, and the deployment.