Getting WooCommerce data into Google Sheets

Almost every store owner ends up here, because Sheets is where the thinking actually happens. The failure mode is always the same: you export everything, the spreadsheet grinds to a halt, and the numbers still don't add up.

Why the obvious approach falls over

The instinct is to export the orders and work it out in the spreadsheet. WooCommerce makes that harder than it sounds, because there is no orders table to export. An order is a row in wp_posts and its real content — email, totals, payment method, shipping — lives in wp_postmeta as one row per field. A raw export gives you a key-value pile, not a report.

So people reach for a CSV export plugin, which flattens some of it, and immediately meets the second wall: Google Sheets caps a spreadsheet at 10 million cells. With 20 columns that's about 500,000 rows, and a sheet becomes sluggish long before that. A store doing a few hundred orders a month is fine for years; a store doing a few hundred a day is not.

The third wall is the one nobody mentions until it bites: the numbers disagree with WooCommerce. Refunds, partially refunded orders, failed and cancelled statuses, tax and shipping in or out — every one of those is a decision, and a raw export makes it silently for you. Two people export the same month and get two different revenue figures.

Three ways to get there

All three end with data in a spreadsheet. They differ in how much of the thinking happens before the export rather than after.

A CSV export plugin

An extension in wp-admin that flattens orders and line items into a CSV you download and import.

  • No technical setup, works from the WordPress admin
  • Fine for a one-off look at a single month
  • The export runs inside WordPress, on the same server as your shop — large exports time out or slow the site
  • You get rows, not answers: the aggregation and the status rules are still yours
  • Manual every single time, or a paid tier for scheduling

Apps Script with a JDBC connection

A script in the sheet connects to your MySQL database directly and pulls query results into a tab on a trigger.

  • Genuinely automatic once it works
  • You control the SQL, so you control the definitions
  • Your production database has to accept connections from Google's IP ranges
  • Every refresh queries the live shop
  • A script nobody else on the team can maintain

Query a PostgreSQL copy, publish the answer

BeQuery

BeQuery clones your store into an isolated PostgreSQL copy. You write the question as SQL against the copy and export a result that's already aggregated.

  • Export hundreds of rows instead of hundreds of thousands — the cell limit stops mattering
  • Status and refund rules live in a saved query, so the number means the same thing every month
  • Nothing runs against your shop, so no export can ever slow down checkout
  • Built-in WooCommerce insights already do revenue, AOV, LTV and repeat rate
  • The sheet pulls from us — we never write into it, so the refresh is on Sheets' schedule (about hourly), not ours
  • The link is a bearer credential: anyone who can read the formula can read the results

The path that stays usable, step by step

  1. 1

    Connect your store to BeQuery

    Read-only MySQL credentials, host and port. BeQuery clones the database into an isolated PostgreSQL copy that only your team can reach — the shop itself is only ever read.

  2. 2

    Write the question as a query, not an export

    This is the step that decides whether the spreadsheet survives. In the SQL editor, aggregate first: revenue by month, margin by product, customers by cohort. You want the answer, which is hundreds of rows, not the raw orders table, which is hundreds of thousands.

  3. 3

    Publish it as a link, or export it once

    Save the query, then either export CSV or XLSX from the editor for a one-off, or publish the query as a CSV link. XLSX exports are capped at 500,000 rows; CSV will happily hand you more than a spreadsheet can hold, which is another reason to aggregate first.

  4. 4

    Paste the formula and let Sheets refresh it

    Drop =IMPORTDATA("…your link…") into a cell. Sheets fetches it on its own — roughly once an hour — so the tab stays current without anyone exporting anything again. Build your charts and pivots on top of that range. The link is a password in URL form: anyone who can see the formula can read the results, and you can revoke it from BeQuery at any time.

Is a spreadsheet the right home for this?

Sheets is genuinely the right tool for a lot of store analysis. It stops being the right tool at a fairly predictable point.

Yes — Sheets earns its place

  • You want to model scenarios by hand, or share numbers with people who live in spreadsheets
  • The analysis changes shape constantly and you want to drag columns around
  • You're combining store numbers with things only you have — targets, costs, a manual budget

You've outgrown it — use a dashboard instead

  • The report is really a dashboard: several people open it and nobody edits a cell
  • The sheet has grown past a few hundred thousand rows and slowed to a crawl
  • What you want is a dashboard that's just correct when you open it

Common questions

Which WooCommerce order statuses should count as revenue?

In practice: wc-completed and wc-processing count, while wc-cancelled, wc-failed and wc-refunded don't, and partial refunds need subtracting separately. The important part isn't which convention you pick — it's picking one and applying it identically every month, which is exactly what a raw CSV export doesn't do for you.

Can a sheet refresh itself, or is this manual every time?

It can refresh itself. Publish a saved query as a CSV link, paste =IMPORTDATA("…") into a cell, and Sheets refetches it on its own — about once an hour. Worth being precise about the direction: BeQuery doesn't write into your spreadsheet, the spreadsheet pulls from BeQuery. Nothing is pushed, nothing is stored in Google on our side, and revoking the link stops it immediately.

How safe is that link?

Treat it as a password. It's fetched anonymously by Google, so the URL itself is the credential — anyone holding it, including anyone who can open your spreadsheet and read the formula, can see that query's results. It's deliberately narrow: one saved query, read-only, row-capped, and revocable. We store only a hash of it, which is why it's shown once when you create it and can't be retrieved afterwards. For anything sensitive shared widely, use Looker Studio on a read-only connection instead.

Why not connect Sheets straight to my shop's database?

Google Apps Script can open a JDBC connection to MySQL, so it is technically possible. It means exposing your production database to Google's IP ranges, writing and maintaining the script, and having every refresh query the same server that serves your customers. It's a lot of moving parts to end up with a spreadsheet that occasionally times out.

How much data can a spreadsheet actually hold?

Google Sheets caps a spreadsheet at 10 million cells across all its tabs. That sounds enormous until you divide by columns: a 20-column export runs out at around 500,000 rows, and performance gets unpleasant well before the hard limit. Aggregated results are never a problem; raw order lines from a busy store are.

See it on your own store first

Free tier, no credit card, read-only access. Production is never written to — and you can explore the demo store before connecting anything.