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.
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.
All three end with data in a spreadsheet. They differ in how much of the thinking happens before the export rather than after.
An extension in wp-admin that flattens orders and line items into a CSV you download and import.
A script in the sheet connects to your MySQL database directly and pulls query results into a tab on a trigger.
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.
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.
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.
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.
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.
Sheets is genuinely the right tool for a lot of store analysis. It stops being the right tool at a fairly predictable point.
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.
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.
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.
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.
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.
Free tier, no credit card, read-only access. Production is never written to — and you can explore the demo store before connecting anything.