Shopify low stock alert: what the admin actually gives you
Shopify does not have a low stock alert. There is no field where you type "tell me at 5," and no notification setting that turns one on.
What it has is two things that get you most of the way there, from opposite directions, and neither of them is called an alert. One is a report that estimates how many days of stock each variant has left. The other is a Shopify Flow trigger that fires every time an inventory number moves, and leaves the threshold for you to write as a condition.
Which one you want depends on whether you are trying to place a reorder or trying to catch a specific product before it embarrasses you. They are different jobs.
Key takeaways
- There is no native threshold setting. The closest native thing is the Inventory remaining per product report, whose stated purpose is "to give an overall sense of how long your tracked inventory is estimated to last, based on your average sales rates for each variant, and the amount of inventory you have left" (read 2026-09-25). That is a planning report, not an alert — it does not contact you.
- Shopify Flow has an inventory trigger, but no low stock trigger. The Flow trigger reference lists Product variant inventory quantity changed, Product variant out of stock, and Product variant back in stock (read 2026-09-25). The first one fires on every movement including restocks; turning it into a low stock alert means adding a condition with a Less than or equal to operator and a number you pick.
- The prediction is only as good as the sales history behind it. Shopify documents that "A product variant's average quantity of items sold per day is calculated based on its sales over the last 28 days", and that when a variant had no sales in the period, "its days of inventory remaining value is set to N/A, as we don't have sufficient data to make a prediction" (read 2026-09-25). Slow sellers — the ones that go quietly out of stock and stay there — are exactly the ones this report declines to predict.
- The blind spot is variants, and it is large. Alerts fire on variants. Your product list, your collection pages and your own attention all work on products. Across 41 live storefronts we read publicly, 15.3% of multi-variant products had some variants sold out and some still available — 1,088 products — while the per-store median was 6.2%. That gap is more than double, so treat the pooled number as a reason to check your own store, not as a description of a typical store.
- You can list your own partly sold-out products in about a minute, without an app. The script further down reads your public product feed and prints the products where some variants are gone and some are not, with the missing option names. It needs no login and writes nothing.
Catalogue figures: 41 live Shopify storefronts, 20,604 products, read 2026-08-26 from the public products.json endpoint; the partial-stockout figures were computed 2026-09-25 by tools/partial_stockout_rollup.py. The stores came at random from a public list of owners who posted their own URL on the Shopify Community's Store Feedback board asking for critique, so read every share here as a reason to check your own store rather than as a figure for Shopify at large. Method at the end.
The alert tells you a number. Someone still has to do the work
Arvio does not send stock alerts, and this article is not about buying one. What it does is the step after an alert would have fired: it reads your live catalogue, surfaces the products where stock, descriptions or fields have drifted, and drafts each fix against your real product data. Nothing applies until you approve it.
Option 1: the report that predicts days remaining
Under Analytics, in the reports filtered to Inventory, there is a report called Inventory remaining per product. Shopify describes its purpose this way:
"The purpose of this report is to give an overall sense of how long your tracked inventory is estimated to last, based on your average sales rates for each variant, and the amount of inventory you have left."
The calculation is documented plainly: "Days of inventory remaining is calculated as:" — and, in the line beneath it — "Total quantity of items still in inventory (ending quantity) divided by the average quantity of items sold per day." And the averaging window is stated: "A product variant's average quantity of items sold per day is calculated based on its sales over the last 28 days." (All read 2026-09-25.)
The chart groups variants into bands — Shopify says it "displays how many product variants are predicted to run out of stock soon (0 days remaining), or have some days of stock remaining (0-30 days, up to 91+ days remaining)."
This is the right tool for reordering. It answers the question what do I need to buy this month, and it answers it in the unit a purchase order is written in.
It is the wrong tool for catching a specific product, for three reasons, all of them documented rather than inferred:
It does not reach out. You go to it. If nobody opens it on a Monday, nothing happens, and the failure is silent.
It skips the variants most likely to surprise you. Shopify states that when a variant "did not have any sales in the selected time period (and thus has an average quantity of items sold per day value of 0), then its days of inventory remaining value is set to N/A, as we don't have sufficient data to make a prediction." A variant that sells two a month is a variant with a real risk of quietly hitting zero, and it is also the variant most likely to land in that N/A bucket.
There is reporting lag on the related sell-through report. For Products by sell-through rate, Shopify notes: "Due to data latency and processing times, the most recent time period that can be returned for this report is approximately two days prior to the current date. For earlier time zones, such as UTC +14:00, this delay is closer to three days." If your decision depends on what happened this weekend, check the dates displayed at the top of the report before you act on it.
There is also a hard floor on history worth knowing before you go looking for last year's pattern: "Historical data for inventory-based metrics go back only to October 1, 2023."
Option 2: Shopify Flow, and the condition you have to write yourself
Flow is where an actual alert comes from, and the shape of it is not obvious from the trigger list.
The Flow trigger reference lists three inventory-related triggers by name: Product variant inventory quantity changed, Product variant out of stock, and Product variant back in stock (read 2026-09-25).
Notice what is not there. There is no inventory below threshold trigger. Product variant out of stock fires when it is already too late — that is the event you are trying to get ahead of. So the alert has to be built from the quantity-changed trigger, which fires on every movement in either direction, plus a condition that filters it down.
The condition is where you supply the threshold. Flow's conditions reference explains the model: "When you set a condition, you choose criteria from fields in the GraphQL Admin API (such as product.title), a logical operator (such as equal to), and a value to check against (such as Blue jeans)." Among the field-level operators it documents is a row headed "Less than and Less than or equal to", described as "Compares values to check whether the first value is less than, or less than or equal to, the second value" (read 2026-09-25).
So the workflow is three steps:
- Trigger: Product variant inventory quantity changed.
- Condition: the variant's available quantity Less than or equal to your number.
- Action: Flow's actions reference states that actions "can also send emails, send Slack messages, or send HTTP requests to external services."
Two things about this are worth knowing before you build it.
It fires on restocks too. The trigger is quantity changed, not quantity dropped. Receive a shipment that brings a variant from 0 to 3, and if your threshold is 5, the condition is still true and you get an alert saying you are low on something you just restocked. That is correct behaviour for the trigger you chose. If it bothers you, the fix is in the condition, not in the trigger.
The threshold is one number applied to things that are not comparable. Five units of a product you sell twice a day is an emergency. Five units of a product you sell twice a month is a year of cover. A single store-wide number will either flood you with alerts about slow sellers or stay quiet about your fastest one. If you are going to run one number anyway, run it against your top sellers only and accept that the tail is not covered by it.
We have not built this workflow in a production store with real order volume behind it, so we are describing what the documented triggers, operators and actions are, not reporting how many alerts a particular threshold produced in a particular store. If you build it, the useful measurement is how many alerts you got in the first week and how many of them you acted on.
The blind spot both options share: variants versus products
Here is the thing that neither the report nor the Flow workflow will tell you, because both of them are correct: they report per variant, and the thing you watch is the product.
Alerts fire per variant. You do not look at variants. You look at the product list, at collection pages, at the storefront. A product with seven sizes where six are gone is a product that shows as available in every one of those places. It has a working Add to cart button. It sits in the collection with no sold-out badge. And it converts badly, because most of the people who click it cannot buy the thing they came for.
We can put a number on how common that state is. Across 41 live Shopify storefronts read from their public product feeds, 7,088 products had more than one variant — 34.4% of the 20,604 products in the sample. Of those multi-variant products, 1,088 had some variants sold out and some still available: 15.3% of multi-variant products, 5.3% of all products read.
The pooled number and the typical store are far apart, and you should use the second one. The per-store median share was 6.2% of multi-variant products, against 15.3% pooled — a ratio of about 2.5. That means a minority of catalogues carry most of the total, which is the normal shape for this kind of measurement and the reason a pooled figure on its own is misleading. The chain of denominators matters here, so it is worth walking down it: of the 41 stores read, 39 had any multi-variant products at all, and of those 39, 11 had nothing in this state — which leaves 28 stores where at least one product was partly sold out. At the top of that range, 6 of the 39 had more than 30% of their multi-variant products partly sold out. Six stores is an existence proof, not a rate: it shows the state can reach that share, and it is far too small a count to read as a proportion of Shopify stores generally.
It is not a small-catalogue artifact. Of the stores at or above 15%, 11 had ten or more multi-variant products and 3 had fewer than ten, so the high shares are not being manufactured by stores with three products and one problem.
What this measurement is not: it is not a count of how much stock anyone has. The public product feed gives one boolean per variant — available or not — and no quantity at all. So none of these figures can tell you how many stores would trip a threshold of 5, or of 2, or of anything else. They describe variants that have already reached zero inside products that have not.
Six sizes gone and the product page still says in stock
Arvio is not the notification this article is about. It reads the catalogue the way a customer meets it, so the products where the listing and the stock disagree are listed rather than waited for, and it drafts the change for you to approve.
List your own partly sold-out products
This takes a minute and needs no app, no login and no permissions. It reads the same public feed any shopper's browser can read, and it writes nothing.
Save this as partial_stock.py and run it with your domain:
#!/usr/bin/env python3
"""List the products in a Shopify store that are partly sold out.
Usage: python3 partial_stock.py yourstore.com
Reads the public /products.json endpoint. No login, no app, nothing written.
"""
import json, sys, urllib.request
if len(sys.argv) < 2:
sys.exit("usage: python3 partial_stock.py yourstore.com")
host = sys.argv[1].replace("https://", "").replace("http://", "").strip("/")
rows, page = [], 1
while page <= 10:
url = f"https://{host}/products.json?limit=250&page={page}"
try:
with urllib.request.urlopen(url, timeout=30) as r:
batch = json.load(r).get("products", [])
except Exception as e:
sys.exit(f"could not read {url}: {e}")
if not batch:
break
rows += batch
page += 1
partial, gone = [], []
for p in rows:
vs = p.get("variants", [])
if len(vs) < 2:
continue
out = [v for v in vs if not v.get("available")]
if not out:
continue
(gone if len(out) == len(vs) else partial).append(
(p.get("title", ""), len(out), len(vs), [v.get("title") for v in out]))
print(f"{len(rows)} products read from {host}")
print(f"{len(partial)} products are PARTLY sold out (page still says in stock)")
print(f"{len(gone)} products are fully sold out\n")
for title, n_out, n_all, names in sorted(partial, key=lambda r: -r[1] / r[2])[:40]:
print(f" {n_out}/{n_all} gone {title[:58]}")
print(f" {', '.join(str(n) for n in names[:6])}")
Here is the output from one run against a store in our sample, on 2026-09-27 — the run recorded in data/partial_stock_live_runs_2026-09-27.json. The counts are a reading of that store on that date, not a fixed result: run it yourself today and the two sold-out counts will differ, because the store is live (the paragraph after the block shows how far the same store had moved from an earlier reading).
$ python3 partial_stock.py guineapigsaustralia.com.au # run 2026-09-27
391 products read from guineapigsaustralia.com.au
34 products are PARTLY sold out (page still says in stock)
6 products are fully sold out
6/7 gone Snuggle In Bundle - Fleece Bed Value Pack for Guinea Pigs
Veggie Patch, Earth Child, Unicorn Dreams, Bee-utiful Sunflowers, Pigs in Space, Sushi Snuggles
36/48 gone Ozzy Cages Kitchenette & Litter Tray for Guinea Pigs & Rab
White / No Liner, White / Yes - Veggie Patch, White / Yes - Space, White / Yes - Unicorns
3/4 gone Tunnel of Love Ramp Cover for Ozzy C&C Ramps
Pink, Grey, White
That is the output the report and the alert do not give you: a list of products to act on, with the specific option names that are missing, rather than a count.
Three things about running it that we hit ourselves:
It reads the live store, not a snapshot, so your numbers will move. Running the script on 2026-09-27 returned 391 products for that store, 34 partly sold out and 6 fully sold out. Our 2026-08-26 reading of the same store had 377 products, 36 partly sold out and 60 fully sold out — the catalogue itself grew, and the fully-sold-out count fell from 60 to 6 in a month. Read those as two independent looks at one store rather than two points on a single series: the August figure came from the separate batch program that built the 41-store file, not from this script, and the two programs page through the catalogue differently. We are not giving you a rate of change from two readings taken by two different programs. The point is narrower and still worth having: whatever this script prints is true of the moment you ran it, which is why a one-off audit is worth less than a repeated one.
A store that returns zero is telling you something real. We also ran it against a store of our own with 77 products and got zero in both categories (both runs are recorded in data/partial_stock_live_runs_2026-09-27.json), which is what a small catalogue with active stock looks like. Zero is a result, not a failure.
It stops after 2,500 products. The loop is written while page <= 10 and each page holds at most 250 products, so a catalogue larger than 2,500 products is read only down to that line and everything past it is silently absent from the counts. Raise the 10 if your catalogue is bigger, and check the first printed line against the product count in your admin before you trust the other two.
Two ways it can fail, and both say so. Run it with no arguments and it exits with usage: python3 partial_stock.py yourstore.com. Point it at a host that does not serve that endpoint and it exits with the URL it tried and the error, for example could not read https://…/products.json?limit=250&page=1: HTTP Error 404: Not Found. A store with password protection on, or one that is not open yet, will fail in that second way — that is the endpoint being closed, not the script being wrong.
The one thing the script cannot do is tell you how many units are left. That number is not in the public feed. To get quantities you need the admin, which is the next section.
Quantities: where the actual numbers live
Everything above works from availability, which is a yes or no. If you want the number, you need to be inside the store, and then you need to know which number you are looking at, because Shopify keeps several and they are not interchangeable.
Shopify tracks these as separate quantity states. Its API reference describes an inventory level as "tracking multiple quantity states like available, on-hand, incoming, and committed", and spells out the two that matter here: "The available argument sets the quantity that's available for sale. onHand argument sets the total physical quantity at the location." (Read 2026-09-25.)
For a threshold, the number that matters is Available. On hand includes units that are already inside unfulfilled orders, so a threshold on On hand will stay quiet while your real sellable stock runs out underneath it.
This distinction is also the most common reason a store thinks its alerting is broken when it is working exactly as configured. If your alert and your admin disagree, check which of these two you are comparing before assuming something failed — that is the inventory not syncing problem, and it is a different job from this one.
When no threshold is the right answer
Some catalogues do not have a threshold to set, and running one anyway produces noise that trains you to ignore the alerts.
Single-unit inventory. Vintage, art, one-off pieces: every variant is 1 until it is 0. There is no gap between "low" and "gone" to alert in.
Made to order or print on demand. If stock is not the constraint, an inventory alert is measuring the wrong thing entirely. Shopify's Continue selling when out of stock setting exists for this case, and if you have it on, the storefront never shows sold out anyway.
Genuine seasonal end-of-run. A product you deliberately let sell through does not need an alert, it needs a decision about the listing once it goes. That decision is a separate question and we have written it up separately: whether to unpublish out-of-stock products or leave them for the SEO, and what happens to sold-out products still showing on collection pages.
A catalogue where you do not know which products matter. If the honest answer to what is my threshold is it depends on the product, and there are thousands of them, then the first job is not alerting, it is knowing what you have — see the inventory management audit for how to get that picture from your own catalogue.
What we can and can't tell you here
The Shopify behaviours in this article — the report calculation and its 28-day window, the N/A rule for variants with no sales, the two-to-three day reporting lag, the three Flow inventory triggers, the Less than or equal to operator, the email and Slack actions, and the definitions of On hand and Available — come from Shopify's own help documentation and Flow reference, quoted with the date we read them. Those are quotations, not our measurements.
The catalogue figures are our own read of 41 public storefronts, described below. They measure which variants had reached zero on the day we read them. They do not measure quantities, they do not measure how many stores would trip any given threshold, and they do not tell us why any particular variant was at zero — none of those stores told us, and plenty of merchants retire a variant deliberately.
We have not run a Flow low-stock workflow in a store with production order volume, so nothing here reports how many alerts a threshold produces in practice.
Method
Sample. 41 live Shopify storefronts, read 2026-08-26, taken at random from a public list of stores whose owners had posted their own URL on the Shopify Community's Store Feedback board. Everything here came from endpoints any browser can fetch. 20,604 products were read in total, with no store hitting a read cap.
What was counted. For each product, the public feed reports each variant with a boolean for availability. A product is counted as partly sold out when it has more than one variant, at least one variant unavailable, and at least one still available. Products with a single variant are excluded from that denominator entirely, since the state does not exist for them.
Whether it looks like you. Stores that ask for feedback skew newer and smaller. The largest bias is survivorship: domains that had stopped serving by the read date were excluded, so the sample is the stores that were still trading. Read the per-store median rather than the pooled share for anything you intend to compare your own store against.
Reproducing it. The aggregation is a script that reads only the stored catalogue file and makes no network requests, so the figures can be recomputed from the same input.
Arvio: AI Store Operator — install it on the Shopify App Store. It does not watch your stock for you; it reads your live catalogue on demand and drafts each fix against your real product data, for you to approve.
FAQ
Does Shopify have a built-in low stock alert?
No. There is no setting where you enter a threshold and receive a notification. The two native routes are the Inventory remaining per product report, which estimates days of stock left per variant but does not contact you, and Shopify Flow, where you combine the Product variant inventory quantity changed trigger with a Less than or equal to condition and an email or Slack action.
How do I set a low stock threshold in Shopify Flow?
The threshold is not part of the trigger, it is a condition you add after it. Start the workflow with Product variant inventory quantity changed, add a condition using the Less than or equal to operator against the variant's available quantity, and set your number there. Then add a Send email or Send Slack message action. Because the trigger fires on every quantity change, the same workflow will also fire when a restock lands below your threshold.
Why does my product still show as in stock when sizes are sold out?
Because availability is tracked per variant and displayed per product. As long as one variant can be bought, the product has a working Add to cart button and no sold-out badge. In our read of 41 storefronts, 15.3% of multi-variant products were in that state, with a per-store median of 6.2%.
Should my low stock threshold be based on Available or On hand?
Available. On hand is the total physical quantity at the location, so it includes units already attached to unfulfilled orders — Shopify's own reporting documentation counts "Committed inventory for orders pending fulfillment" inside your stock on hand. A threshold on On hand can stay silent while the stock you can actually sell runs out.
Why does the inventory remaining report show N/A for some products?
Shopify sets days of inventory remaining to N/A when a variant had no sales in the selected period, because the calculation divides remaining stock by average daily sales and there is no average to divide by. This means slow-moving variants, which are often the ones that go quietly out of stock, are the ones the report declines to predict.
Can I check my catalogue for partly sold-out products without installing an app?
Yes. The public product feed at /products.json lists each product's variants with an availability flag, so a short read-only script can print the products where some variants are gone and some are not. The script in this article does that and needs no login. It cannot show quantities, because the public feed does not include them.
How far back does Shopify inventory reporting go?
Shopify states that historical data for inventory-based metrics goes back only to October 1, 2023, and that data before that date is not available for inventory-based metrics. Inventory for deleted locations does not appear in historical reporting either.
