A Shopify abandoned cart recovery tracker in Google Sheets, built with n8n, logs every abandoned checkout to a spreadsheet and flips each row to Recovered the moment the shopper comes back and buys. You always know which carts are still open, which were saved, and how much revenue you clawed back. It runs on two Shopify triggers and one shared sheet, needs no paid recovery app, and costs effectively nothing to run.
Prefer to skip the setup? The ready-made template imports in under 10 minutes. Jump to the template section below.
What it does
Most stores treat abandoned carts as a black box: Shopify sends one or two recovery emails, and whatever happens after that is invisible. This workflow turns that black box into a living ledger.
Every time a shopper starts a checkout and walks away, n8n catches the event and writes a row to a Google Sheet: who they were, what was in the cart, how much it was worth, the recovery link, and a status of Abandoned. When that same shopper later returns and completes an order, a second flow finds the matching row by checkout ID and flips it to Recovered, stamping the date and the amount saved.
The result is a spreadsheet you actually own. Filter it to see every open cart from this week, sort by value to chase the biggest ones first, or sum the Recovered column to see exactly how much abandoned revenue you brought back. It slots neatly alongside the rest of your n8n Shopify automation stack.
Why it beats the default
Shopify already shows an abandoned checkout report, so why build this? Because the native report is a dead end. You cannot edit it, you cannot add a note, you cannot join it to anything, and you certainly cannot hand a filtered slice of it to an assistant to work through.
- It is yours. The data lives in a sheet you control. Add columns, pivot it, build a chart, or pipe it into another tool later.
- It tracks outcomes, not just events. The native report lists abandoned checkouts. This one tells you which ones were recovered and how much money came back, in the same row.
- No per-message billing. There is no recovery app subscription and no email-credit meter. The whole thing runs on webhooks and a free Google Sheet.
- It is a foundation. Once the ledger exists, layering on a Gmail nudge, a Slack alert for high-value carts, or a weekly digest is a small addition rather than a new project.
What you need
- A Shopify store on any paid plan, with an app that has read access to
ordersandcheckouts. - An n8n instance, either n8n Cloud or self-hosted.
- A Google account and one Google Sheet for the tracker.
- About 30 minutes to build from scratch, or under 10 with the template.
How it works — the big picture
ABANDONED CART RECOVERY TRACKER
Flow A — log abandoned carts
[Shopify Trigger: checkouts/update] -> [Filter: has email, not paid]
-> [Edit Fields: build row, Status = Abandoned]
-> [Google Sheets: Append or Update on Checkout ID]
|
v
+----------------------+
| Google Sheet ledger |
| (one row per cart) |
+----------------------+
^
|
Flow B — mark recovered
[Shopify Trigger: orders/create] -> [Edit Fields: map checkout_id, Status = Recovered]
-> [Google Sheets: Append or Update on Checkout ID]
Node-by-node list
Seven nodes across two independent flows that share one sheet:
| # | Node | Type | Job |
|---|---|---|---|
| A1 | Catch Abandoned Checkout | shopifyTrigger |
Fires on the checkouts/update topic |
| A2 | Only Real Abandoned Carts | filter |
Keeps items with an email and no completed_at |
| A3 | Build Tracker Row | set (Edit Fields) |
Shapes the row, sets Status to Abandoned |
| A4 | Log to Sheet | googleSheets |
Append or Update, matched on Checkout ID |
| B1 | Catch New Order | shopifyTrigger |
Fires on the orders/create topic |
| B2 | Mark Recovered | set (Edit Fields) |
Maps checkout_id, sets Status to Recovered |
| B3 | Update Sheet | googleSheets |
Append or Update, matched on Checkout ID |
Step-by-step build
Build the sheet first, then the two flows.
- Create the Google Sheet. Make a new sheet named
Trackerwith these headers in row 1:Checkout ID,Date,Customer,Email,Items,Cart Value,Currency,Recovery URL,Status,Recovered At. The exact spelling matters because n8n matches on these names. - Add the abandoned checkout trigger. Add a Shopify Trigger node, connect your Shopify credential, and set Topic to
Checkout Updated(checkouts/update). Shopify fires this when a cart is started and then sits idle. - Filter to real abandoned carts. Add a Filter node after the trigger. Add two conditions, combined with AND:
{{ $json.email }}is not empty, and{{ $json.completed_at }}is empty. This drops anonymous carts and any checkout that already became an order. - Shape the row. Add an Edit Fields node (Set). Switch to Manual mapping and add one assignment per column. For example, map
Checkout IDto={{ $json.id }},Emailto={{ $json.email }},Cart Valueto={{ $json.total_price }},Recovery URLto={{ $json.abandoned_checkout_url }}, and setStatusto the fixed stringAbandoned. - Log to the sheet. Add a Google Sheets node, Operation Append or Update Row. Pick your document and the
Trackersheet, then set Column to match on toCheckout ID. Map each column to the field you built in the Edit Fields node. Append or Update is what keeps one row per cart even when Shopify fires the event several times. - Add the recovery trigger. Add a second Shopify Trigger node with Topic
Order Created(orders/create). This is a separate entry point, not wired into Flow A. - Mark the cart recovered. Add an Edit Fields node after it. Map
Checkout IDto={{ $json.checkout_id }}, setStatustoRecovered, mapRecovered Atto={{ $now.toISO() }}, and mapCart Valueto={{ $json.total_price }}so the saved amount reflects the real order. - Update the same row. Add a final Google Sheets node, Operation Append or Update Row, matched on
Checkout IDagain. Because the abandoned row already carries that checkout ID, this lands on it and flips the status instead of adding a new line. - Save and activate. Save the workflow and toggle it Active. Run a test checkout, then complete it, and watch the row go from Abandoned to Recovered.
Tip: Shopify orders carry the originating checkout_id, which is the clean join key between the two flows. Matching on checkout ID rather than email means two different carts from the same shopper never collide.
The data structure
A row moves through two states. Here is the same cart before and after recovery.
| Column | Abandoned row | Recovered row |
|---|---|---|
Checkout ID |
29845011 | 29845011 |
Customer |
Emily Rodriguez | Emily Rodriguez |
Email |
emily.rodriguez@gmail.com | emily.rodriguez@gmail.com |
Cart Value |
128.00 | 128.00 |
Currency |
USD | USD |
Status |
Abandoned | Recovered |
Recovered At |
(empty) | 2026-06-26T15:42:00Z |
Header names in the sheet must match the names you map in n8n exactly, including capitalization. A mismatch silently creates a new column instead of writing to the one you meant.
Common mistakes
- Skipping the
completed_atfilter. Without it, every checkout update, including completed ones, lands in the tracker and inflates your abandoned count. - Using Append instead of Append or Update. Plain Append adds a fresh row on every Shopify fire, so one cart becomes five rows. Always match on Checkout ID.
- Matching the recovery flow on email. Email is not unique per cart. Match on
checkout_idso a repeat shopper does not overwrite an unrelated open cart. - Wrong Set node version. Use the current Edit Fields node with named assignments. Old tutorials show a key-value table that behaves differently and breaks expressions.
- Confusing the topics. Use
checkouts/updatefor abandonment andorders/createfor recovery.checkouts/createfires too early, before the cart is meaningfully abandoned.
Cost at realistic volume
This is one of the cheapest automations you can run because it never touches a paid messaging service.
| Volume | Executions / month | n8n cost | Google Sheets |
|---|---|---|---|
| 100 abandoned carts | ~150 | Free (self-hosted) or Starter | Free |
| 500 abandoned carts | ~750 | Within n8n Cloud Starter | Free |
| 2,000 abandoned carts | ~3,000 | n8n Cloud Pro or self-hosted | Free |
Self-hosted n8n makes the marginal cost zero. On n8n Cloud, even a busy store stays inside an entry plan, since each cart costs only one or two executions across both flows.
Get the abandoned cart recovery tracker template
The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.
Instant download · Works on n8n Cloud and self-hosted
Frequently asked questions
Does this work on Shopify Basic?
Yes. The workflow uses standard Shopify webhooks, checkouts/update and orders/create, that are available on every paid Shopify plan, including Basic. You only need an app with read access to orders and checkouts to issue the API credential.
What counts as an abandoned cart here?
Any checkout that has a customer email but no completed_at timestamp. The Filter node enforces that, so anonymous carts and already-paid checkouts never reach your sheet. A cart is treated as recovered only when a real order arrives carrying the same checkout ID.
Will the same cart create duplicate rows?
No. Both Google Sheets nodes use Append or Update matched on Checkout ID. Shopify can fire checkouts/update several times for one cart, but each fire lands on the same row, so the tracker stays one row per cart from open to recovered.
Can I send a recovery email from the same workflow?
Yes. After the log node you can add a Gmail node that uses the Recovery URL field to email the shopper. Many stores keep the tracker pure and run outreach as a separate workflow, so the ledger and the email sequence stay independent and easy to debug.
How is this different from Shopify’s built-in report?
Shopify shows a fixed report you cannot edit or join. This gives you an owned spreadsheet you can filter, pivot, hand to an assistant, and read recovered revenue from directly, with no per-email cost and no third-party recovery app subscription.