HomeShopify & E-commerceHow to Build a Shopify Multi-Location…

How to Build a Shopify Multi-Location Inventory Report with n8n

How to Build a Shopify Multi-Location Inventory Report with n8n









A Shopify multi-location inventory report in n8n gives you one clean view of how much stock sits in each warehouse and retail branch, refreshed on a schedule and written straight to a Google Sheet. If you sell from more than one location, the Shopify admin makes you click into each product to see the per-location split, and there is no built-in export that lays it all out side by side. This guide builds that export with four nodes, and the same workflow is available as a ready-to-import template at the end.

What it does

Once a week, this workflow asks Shopify for your catalog and the stock level of every variant at every location, flattens that nested data into simple rows, and appends them to a Google Sheet with the run date attached. The result is a running log you can filter by location, sort by lowest stock, or drop into a pivot table to compare warehouses.

Each row answers a single question: how many units of this variant are available at this location, as of this date. Over a few weeks the Sheet becomes a stock-movement history you can chart, without touching a paid inventory app.

  ┌────────────────────────────────────────────────────────────────┐
  │  SHOPIFY MULTI-LOCATION INVENTORY REPORT                        │
  │                                                                │
  │  [Schedule: weekly] → [Shopify GraphQL] → [Code: flatten]      │
  │                                              ↓                 │
  │                                   [Google Sheets: append]      │
  └────────────────────────────────────────────────────────────────┘
  

Why it beats the default

Shopify’s own inventory screen shows one product at a time. To answer “where is my stock low across all warehouses this week”, you would open dozens of products by hand, or pay for an app that does roughly this and charges monthly. The report you build here is yours, runs on your own schedule, and lands the data where you already work with numbers.

The technical win is the single GraphQL query. The older REST approach needs one call for your locations, one for inventory levels, and one for variant titles, then a merge to join them by ID. GraphQL returns the product title, variant, SKU, location name, and available quantity together in one response, so you replace three calls and a merge with a single HTTP Request node. Fewer calls means you stay comfortably inside Shopify’s rate limits even as the catalog grows. This report is one piece of a wider stack you can assemble with n8n Shopify automation.

What you need

  • An n8n instance (Cloud or self-hosted), version 1.0 or newer.
  • A Shopify store with two or more locations set up under Settings, Locations.
  • A Shopify Admin API access token with the read_products and read_inventory scopes. Create it with the 2026 Dev Dashboard method, covered in the connect Shopify to n8n guide. Do not use the old admin custom-app flow; it was removed.
  • A Google account and one empty Google Sheet to receive the rows.

Building from scratch takes about 30 minutes. Importing the template takes under 10.

Node-by-node list

# Node Type Job
1 Weekly Monday 7am Schedule Trigger Fires the run once a week
2 Get Inventory (Shopify GraphQL) HTTP Request One GraphQL call for products, variants, locations, stock
3 Flatten To Rows Code Turns nested JSON into one row per variant per location
4 Append To Report Sheet Google Sheets Appends every row with the run date

Step-by-step build

1. Add the Schedule Trigger

Add a Schedule Trigger node. Set the interval to Weeks, every 1 week, trigger on Monday at hour 7. That gives you a fresh snapshot at the start of each week. You can change this to daily if you move stock quickly, but weekly keeps the Sheet readable.

2. Call Shopify with one GraphQL query

Add an HTTP Request node named Get Inventory (Shopify GraphQL) and configure it:

  • Method: POST
  • URL: https://YOUR_STORE.myshopify.com/admin/api/2026-04/graphql.json
  • Authentication: Predefined Credential Type, then Shopify Access Token API, and select your credential.
  • Send Body: on. Body Content Type: JSON.

Paste this into the JSON body field:

{
  "query": "query { products(first: 50) { edges { node { title variants(first: 20) { edges { node { sku title inventoryItem { inventoryLevels(first: 10) { edges { node { location { name } quantities(names: [\"available\"]) { name quantity } } } } } } } } } } }"
}
📌

The query asks for quantities(names: ["available"]), the current Admin API way to read stock. It needs API version 2026-04 and the read_inventory scope, or the field comes back empty.

3. Flatten the response with a Code node

The GraphQL response is deeply nested: products contain variants, variants contain inventory levels, each level has a location and a quantity. Add a Code node named Flatten To Rows to turn that into flat rows:

const resp = $input.first().json;
const products = (resp && resp.data && resp.data.products && resp.data.products.edges) || [];
const reportDate = new Date().toISOString().slice(0, 10);
const rows = [];

for (const p of products) {
  const productTitle = p.node.title;
  const variants = (p.node.variants && p.node.variants.edges) || [];
  for (const v of variants) {
    const item = v.node.inventoryItem || {};
    const levels = (item.inventoryLevels && item.inventoryLevels.edges) || [];
    for (const l of levels) {
      const q = (l.node.quantities || []).find((x) => x.name === 'available');
      rows.push({ json: {
        report_date: reportDate,
        product: productTitle,
        variant: v.node.title || '',
        sku: v.node.sku || '',
        location: (l.node.location && l.node.location.name) || '',
        available: q ? q.quantity : 0,
      }});
    }
  }
}
return rows.length ? rows : [{ json: { report_date: reportDate, product: '', variant: '', sku: '', location: '', available: 0 } }];

After this node, each item looks like a single tidy record ready for a spreadsheet:

{
  "report_date": "2026-07-31",
  "product": "Trailhead Merino Hoodie",
  "variant": "Medium / Slate",
  "sku": "HD-MER-M-SLT",
  "location": "Austin, TX Warehouse",
  "available": 42
}

4. Append rows to Google Sheets

Add a Google Sheets node named Append To Report Sheet. Set Operation to Append, pick your document and the target sheet (gid=0 is the first tab), and set the mapping to Map Automatically. Because the Code node already emits the exact column names, add a header row to your Sheet once with these columns:

Column Type Example
report_date Date 2026-07-31
product Text Trailhead Merino Hoodie
variant Text Medium / Slate
sku Text HD-MER-M-SLT
location Text Austin, TX Warehouse
available Number 42

Save the workflow and run it once by hand. Your Sheet fills with one row per variant per location. From here, a filter by location or a sort by available ascending gives you the low-stock picture in seconds.

Common mistakes

  • Missing the read_inventory scope. If every row shows available: 0, your token lacks read_inventory. Recreate the token in the Dev Dashboard with both product and inventory read scopes.
  • Wrong API version. The quantities(names: ...) field needs a 2024-04 or newer Admin API. Keep 2026-04 in the URL. Older versions used a flat available field that behaves differently.
  • Only 50 products returned. The template requests the first 50 products. For a larger catalog, read pageInfo.hasNextPage and pageInfo.endCursor, then loop the HTTP Request with an after cursor until there are no more pages. Do not raise first above 250; that is the hard ceiling per page.
  • Sending the body as raw text. In the HTTP Request node, set Body Content Type to JSON and paste into the JSON field. Pasting the query as raw text or form data makes Shopify reject it with a parse error.
  • Sheet columns out of order. Auto mapping matches on header names, not position, so your header row must spell the six column names exactly as the Code node outputs them.

Cost at realistic volume

Every service in this build sits on a free tier at report volume. A weekly run is four executions a month, nowhere near any n8n Cloud plan limit, and self-hosted n8n is free to run. Google Sheets is free. Shopify’s Admin API costs nothing to query; a single weekly GraphQL call is a rounding error against your rate limit.

Service Usage per month Cost
n8n (self-hosted) ~4 executions $0
Shopify Admin API ~4 GraphQL calls $0
Google Sheets ~4 appends $0

Run it daily instead of weekly and you are at roughly 30 executions a month, still free on every tier. The only real cost is the 30 minutes to build it, which the template removes.

Ready-to-import template

This guide is free to follow top to bottom. If you would rather skip the build, the ready-to-import template drops all four nodes onto your canvas already wired, so you only add your Shopify and Google credentials and pick your Sheet. Prefer it fully done for you? See our done-for-you service.

Download the template ($14) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Does this report work with Shopify’s multiple locations feature?

Yes. The GraphQL query reads inventoryLevels for every location attached to each variant, so a store with a main warehouse plus two retail branches returns a row per location automatically. You do not list your locations anywhere in the workflow; Shopify returns whatever locations hold stock for that item.

Why use GraphQL instead of the REST inventory_levels endpoint?

REST forces you to fetch locations, inventory levels, and variant titles as three separate calls and stitch them together by ID. One GraphQL query returns the product title, variant, SKU, location name, and available quantity together, so a single HTTP Request node replaces three plus a merge, which is fewer nodes and far fewer API calls.

How many products does the query cover?

The template requests the first 50 products, each with up to 20 variants and 10 locations. That covers most small and mid-size catalogs in one run. For larger stores you add cursor pagination on the products connection, looping until hasNextPage is false, which the guide explains in the common mistakes section.

What Shopify permission scope does it need?

Your access token needs read_products and read_inventory. Add both scopes when you create the app in the Shopify Dev Dashboard, then generate the Admin API token. Without read_inventory the inventoryLevels field returns null and your report shows zero available for every location.

Can I send the report to Slack or email instead of Google Sheets?

Yes. The Code node outputs plain rows, so you can swap the Google Sheets node for a Gmail or Slack node, or add one after it. A common pattern is to keep the Sheet as the archive and add a Gmail node that emails a short summary of any location where available drops below a threshold.

Related guides