Shopify orders to Google Sheets: why a sheet, and what it feeds
A sheet of orders is a staging area, not a report. What belongs in it, the Sheets limits that end the approach, and when to move to a database instead.
On this page
- What we'll cover
- What the sheet is actually for
- What belongs in a row
- The line item problem
- Building it with CodeWords
- Orders change after they are placed
- What the sheet feeds downstream
- When to move to a database
- Protecting the sheet from the people using it
- Making it survive
- Limits worth knowing about
- What to build first
- Frequently asked questions
- Related reading
Pushing Shopify orders into a spreadsheet is one of the first automations most merchants build, and it is usually described as reporting. It is not really reporting — Shopify's own analytics does that better — and understanding what the sheet is actually for determines whether it stays useful or becomes another tab nobody opens.
The sheet earns its place as a staging area: somewhere order data can be joined to things Shopify does not know about, shaped by whoever needs it without asking an engineer, and fed into whatever comes next. That framing changes what belongs in it, and it also tells you when you have outgrown it.
What we'll cover
- What the sheet is actually for
- What belongs in a row
- The line item problem
- Building it with CodeWords
- Orders change after they are placed
- What the sheet feeds downstream
- When to move to a database
- Protecting the sheet from the people using it
- Making it survive
- Limits worth knowing about
- What to build first
- Frequently asked questions
What the sheet is actually for
Joining to things Shopify does not know — ad spend, supplier costs, shipping actuals, a returns log kept elsewhere.
Shaping without engineering. An operator who wants a different cut can make one in a spreadsheet and cannot in an analytics dashboard.
Feeding other things. Fulfilment partners, accountants, forecasting models and other automations all read a sheet without integration work.
Keeping history that Shopify's reporting collapses, particularly around products and variants that have since changed.
Being handed to people without Shopify access, which is most accountants and most suppliers.
Not for dashboards. If the output is a chart somebody looks at weekly, Shopify's analytics or a proper BI tool is the better answer and the sheet is a detour.
What belongs in a row
Order number and the internal id, since the display number is not stable across every context.
Timestamps in one timezone, stated in the header, because this single decision causes more reconciliation confusion than anything else here.
Financial and fulfilment status, both, which are independent and frequently confused.
The money broken out: subtotal, discounts, shipping, tax, total, and the currency. A single total is useless the first time anybody asks a question.
Customer identifier rather than customer details, keeping personal data out of the sheet unless there is a reason it must be there.
The channel and any attribution you hold, since this is the column most often wanted and most often forgotten.
A last-updated stamp, which makes the whole thing debuggable.
The line item problem
The decision that determines whether the sheet works, and the one usually made by accident.
One row per order is readable and cannot answer anything about products.
One row per line item answers product questions and makes order counts wrong for anyone who forgets to deduplicate.
Two sheets is usually right: orders, and line items keyed to them. Slightly more work, and it avoids the argument entirely.
Never both shapes in one sheet. A sheet where some rows are orders and some are line items will be summed incorrectly within a week.
Keep the product title as it was at the time, alongside the current id, since renamed products silently rewrite history otherwise.
Say which sheet is authoritative for revenue, in the header, because two sheets produce two totals and somebody will circulate the wrong one.
Building it with CodeWords
CodeWords connects to more than 3,000 integrations, and the connection is made once and reused.
- Open CodeWords and start a new automation.
- Describe what should happen in plain language to Cody, the automation builder: which orders, which fields, which sheet, and how updates are handled.
- Authorize the connections — Shopify and Google Sheets.
- Describe the exceptions: a cancelled order, a partial refund, a test order, a duplicate webhook.
- Backfill a known month first and reconcile the total against Shopify's own reporting before trusting it.
You describe the outcome; Cody builds it, connects it, and deploys it. The free plan covers light use, with Pro at $39 per month and Business at $100 per month as usage grows; details are on the pricing page.
Orders change after they are placed
This is where most naive implementations quietly become wrong, because an append-only sheet assumes orders are events and they are not.
Refunds, full and partial, which change the revenue of an order that was recorded days ago.
Cancellations, which should not simply vanish from the sheet, since a disappearing row is indistinguishable from a broken sync.
Edits. Shopify allows order editing, so quantities and totals change after the fact.
Fulfilment status moves through several states, and a sheet capturing only the first is describing intent rather than outcome.
Append-only is therefore wrong. Update the row in place, keyed on the order id, and keep the last-updated stamp so the change is visible.
Or record the adjustments as their own rows in a separate sheet, which is more work and is the better answer if anyone needs to reconcile against accounting.
What the sheet feeds downstream
The reorder signal. Units sold by variant over a window, against a threshold, which is the most valuable thing a merchant can get out of this.
Supplier and fulfilment exports, shaped as the partner wants rather than as Shopify emits.
The accounting reconciliation, with the tax and shipping broken out, which is the join an accountant asks for every month.
Contribution margin, once supplier costs are joined in, which Shopify cannot compute because it does not know your costs.
Ad spend against revenue by channel, joined from the ad platforms, which is the other half of the attribution question.
Alerts on anomalies — a sudden drop in orders, an unusual order value, a spike in a single variant — read off the sheet rather than built separately.
When to move to a database
Worth naming the thresholds in advance, because the migration always happens under pressure otherwise.
Row count. Sheets has a hard cell limit, and a busy store with line items reaches it faster than expected.
Recalculation speed. When opening the sheet takes noticeable time, it has stopped being a working tool.
More than a couple of joins. Spreadsheet formulas across several sheets are a database query written in a worse language.
Multiple writers. Several automations writing the same sheet will conflict, and the failure is silent.
Needing history. Point-in-time reconstruction is natural in a database and painful in a sheet.
The migration is easier than it looks if the sheet was built with stable ids and explicit timestamps, which is the strongest argument for those two habits at the start.
Protecting the sheet from the people using it
A sheet's advantage is that anyone can change it, which is also the thing that breaks it. A few conventions keep a working sheet working.
Keep the synced data on its own tab and do nothing else there. Analysis lives on other tabs referencing it.
Protect the synced range so a stray paste cannot overwrite what the automation wrote.
Never insert manual columns inside the synced block. They will be overwritten or will shift the write position, both of which fail quietly.
Put manual annotations on a separate tab, keyed on the order id, and join them.
Freeze the header and state the definitions there — the timezone, which sheet is authoritative for revenue, what each status column means.
Expect a copy to be made. Somebody will duplicate the sheet to experiment and then circulate a number from the stale copy, so put the last-updated stamp somewhere visible enough to catch that.
Making it survive
Key on the order id and upsert, never append blindly, since webhooks are delivered at least once.
Batch the writes. Row-by-row updates hit Sheets' quotas quickly on a busy store.
Reconcile daily against Shopify's order count for the day and alert on a mismatch.
Handle the backfill separately from the live sync, with pagination, since the two have different failure modes.
Report the outcome: rows written, rows updated, and any discrepancy against Shopify's own count.
Limits worth knowing about
Sheets has cell and API quotas, and both are reachable — the cell limit by volume, the quota by chatty writes.
Shopify webhooks are at-least-once and occasionally out of order, so idempotency is mandatory rather than defensive.
Rate limits differ by API, and the REST and GraphQL limits behave differently enough to matter when backfilling.
Currency needs care for multi-currency stores, where presentment and shop currency are different numbers for the same order.
Personal data in a shared sheet is a real obligation, and a sheet shared by link is shared further than intended more often than anyone admits.
What to build first
The daily reconciliation: order count and revenue total for yesterday, from Shopify, compared against what your sheet holds, with an alert on any difference. Build it before the sync is trusted, because a sheet that silently drifts is worse than no sheet, and this check is what tells you the difference.
Two habits make the difference. Upsert on the order id from the beginning rather than appending, since retrofitting that after a month of duplicated rows means reconciling by hand. And write timestamps in one stated timezone in the header, because every disagreement about these numbers is eventually a disagreement about which day an order belonged to.
Frequently asked questions
Is a Google Sheet the right place for Shopify order data?
As a staging area, yes — for joining to data Shopify does not hold, for sharing with people without store access, and for feeding other tools. For dashboards, Shopify's own analytics or a BI tool is better.
Should I use one row per order or per line item?
Two sheets: orders, and line items keyed to them. One row per order cannot answer product questions; one per line item makes order counts wrong. Mixing both shapes in one sheet guarantees a bad total within a week.
What happens when an order is refunded or edited?
The row must be updated in place, keyed on the order id. Append-only syncs treat orders as immutable events, which they are not — refunds, cancellations, edits and fulfilment changes all alter an order recorded days earlier.
Why do my sheet totals disagree with Shopify?
Usually timezone boundaries, or a refund that the sheet never picked up, or duplicated rows from at-least-once webhook delivery. A daily reconciliation against Shopify's own count identifies which.
How do I avoid duplicate rows?
Key on the order id and upsert. Shopify delivers webhooks at least once and sometimes out of order, so idempotency is a requirement rather than a precaution.
When should I stop using Sheets for this?
When you approach the cell limit, when opening the sheet is slow, when you need more than a couple of joins, when several automations write to it, or when you need point-in-time history.
What is the most valuable thing the sheet feeds?
The reorder signal — units sold by variant over a window against a threshold. It is the output merchants act on most often and the one Shopify's reporting is least shaped for.
Should customer details go in the sheet?
Only if there is a specific reason. A customer identifier is usually enough, and a sheet shared by link travels further than intended more often than people admit.
How do I stop people breaking the synced sheet?
Keep the synced data on its own protected tab, put analysis and manual annotations on separate tabs joined by order id, and never allow manual columns inside the synced block — they get overwritten or shift the write position.
Why does a stale copy of the sheet matter?
Because someone will duplicate it to experiment and then circulate a number from the copy. A visible last-updated stamp is usually enough to catch it.