A customer orders four things on a Monday night. Three are in the bin. The fourth sold out ninety minutes earlier because your marketplace listing took the last two. Your Flow workflow does exactly the right thing and puts the fulfillment order on hold with the reason set to insufficient inventory. Nothing goes out half-picked. Nobody gets a surprise partial box. So far the system worked.

The pallet lands on Wednesday morning. Someone counts it in, the available quantity at that location goes from zero to sixty, the back in stock emails fire, three new customers buy it, and those three orders ship on Wednesday afternoon. Monday's order is still on hold, because nothing in Shopify noticed that the thing it was waiting for had turned up. It ships on Friday, when someone remembers to filter the orders list by On hold. Or it ships next Tuesday, when the customer emails to ask whether you forgot.

That is the entire problem, and it is more annoying than it is complicated. The hold worked. The restock worked. The piece in the middle does not exist.

What Shopify actually gives you

Credit first. Fulfillment holds are a real primitive, not a tag pretending to be one. Flow's Hold fulfillment order action takes a reason and reason notes, and a held fulfillment order genuinely will not be picked, requested, or pushed to a fulfillment service until the hold comes off. The matching Release fulfillment order holds action flips the status back from On hold to Open. Both are on every plan. If you are not holding short orders automatically today, that workflow is twenty minutes of work and you should build it before you finish this article.

On the trigger side there is Fulfillment order ready to fulfill, which fires when an order has cleared risk assessment and has inventory available, and which usefully fires again after a hold is released and the fulfillment order returns to open. So the loop looks closeable. It is not.

There is no trigger for the inventory a held order was waiting on arriving. A Shopify staff member said as much on the forums in 2023, in a thread titled almost exactly the way people search for it, and merchants were still landing on that same thread confirming nothing had changed in July 2026. Three years is a long time for a gap this specific to stay open.

Shopify will happily hold an order for you. It will not tell you when it is time to stop holding it.

The obvious reach is the Inventory quantity changed trigger. It fires reliably and it hands you the inventory item and the new quantity. It does not hand you the orders waiting on that item, and Flow gives you no way to search backward from a variant to the held fulfillment orders that contain it. That is precisely where every merchant who tries this gets stuck, and it is not a skill issue. The relationship you need to traverse is not exposed to the workflow.

The polling workaround, and its four sharp edges

There is a more native path than there used to be. Flow now has a Get fulfillment order data action, and the example query in Shopify's own documentation is literally status:ON_HOLD. Pair that with the Scheduled time trigger and a For each loop and you can poll: every thirty or sixty minutes, fetch the held fulfillment orders, check each one, release the ones that are now coverable. People do run this. It is the best Flow-only answer available.

It also has four edges that will cut you, and three of them cut quietly.

Release is all or nothing. The Flow release action has no fields at all. It releases every hold on that fulfillment order. If a fraud review, a subscription app, an address check, or a human also placed a hold, your inventory automation just cleared all of them. Shopify's API documentation is unusually blunt about this, warning that releasing all holds "will result in the fulfillment order being released prematurely and items being incorrectly fulfilled." The underlying fulfillmentOrderReleaseHold mutation accepts a holdIds argument for exactly this reason, so an app can release only the hold it placed and tag it with an externalId. The Flow action does not expose either field. This is the same class of mistake as clearing a high risk flag you did not read, except an automation does it at three in the morning.

Availability has to be checked at the assigned location. Sixty units in the Nevada 3PL do not make a fulfillment order assigned to Pennsylvania ready to ship. Store-wide availability is the wrong number and it is the number that is easiest to reach for, which is a bad combination. Release on it and you have simply moved the failure downstream to a location that cannot pick the order.

The result cap has a nasty failure mode. Get data actions return at most 100 records per run. If more than a hundred fulfillment orders are on hold, you work on a subset, and depending on your sort order it can be the same subset every run, forever, while the tail ages out of sight. Shopify's documentation also warns that a failed query often returns all results rather than none. For a workflow whose next step is "release the hold," that is not a warning to skim.

Available is not reserved. Two held orders waiting on the same three units will both look coverable in the same polling run, because checking availability does not claim anything. Both get released, the second picker finds an empty bin, and you are back where you started with a worse audit trail. It is the overselling problem wearing a different hat.

None of this makes polling wrong. It makes it software, with concurrency, ordering, idempotency, and a rollback story. Flow is a good place to express a rule. It is not a good place to write software.

Why the manual version wins for a while, and then does not

Filter the orders list by On hold, scan it, open the ones that look close, check the location, release, repeat. At eight held orders that is four minutes and you should not automate it. At sixty it happens on Monday morning and again on Thursday afternoon, and everything that becomes shippable in between just waits.

What makes it invisible is that Shopify reports nothing on this. There is no number anywhere in the admin for the average hours between stock landing and the hold coming off, which is the only metric that matters here. The cost shows up somewhere else entirely: delivery promises missed by two days, where is my order tickets, and cancellations from customers who reasonably concluded you had lost the order. The money was already collected. The stock was already in the building. The two facts just never met.

And it degrades exactly when you can least afford it. In peak you have more held orders, more restocks per day, and less time to look at either.

What the automation actually has to do

The working version is not a better poll. It is the arithmetic a good fulfillment lead does when they walk past the receiving dock and think "hang on, doesn't that unblock the Wednesday orders." As a Dugong playbook, in plain prose, it looks like this:

# trigger
On stock received at a location, and on a short
schedule as a safety net

# steps
1. Find held orders whose missing lines include what
   just landed
2. Check availability at the assigned location, not
   store-wide
3. Reserve before releasing, so two orders never
   claim the same unit
4. Release only the hold this automation placed, by
   its hold ID
5. Rank by promise date and age, oldest and latest
   first, not whatever returned first
6. Ship partial when waiting costs more than a
   second label
7. Tell the customer, once, when the wait ran long

Steps three and four are where a rules engine gives out. Reserving before releasing means the second order in the same batch sees two units left, not five, and stays held instead of being released into an empty bin. Releasing by hold ID means the fraud hold your risk workflow placed an hour ago survives the inventory release, which is the difference between an automation you can leave running and one you have to supervise.

Step five sounds like housekeeping and is not. When a restock covers nine of the twelve orders waiting on it, which nine you release decides which three customers get an apology. Oldest first is a decent default. Nearest promised delivery date is better. Whatever came back first from a paginated query is not a policy.

Step six is the judgement call that no setting can hold. If the missing line is a four dollar accessory and the rest of the order has been sitting for six days, ship the six days of stock now and send the accessory after. If the wait is another two days on a set that only makes sense together, hold it. That answer depends on the promised date, the margin left after a second label, and how far away the item actually is, and it changes order by order. It is a calculation, not a checkbox.

◆ NOTE The trap is treating the hold as the end of the workflow. Holding is defensive, satisfying, and easy to build, so it gets built. Releasing is the half that earns the money and nobody builds it, so the held queue becomes a place orders go rather than a place they pass through. Every hold you automate without a matching release path is a small stack of paid revenue sitting in a room.

Why this is a compiler problem, not an app problem

Every decision in that list depends on context that cannot be pre-ranked. Is this customer's promised date already blown, so the right move is partial now and an apology. Is the count that just landed trustworthy, or is it a supplier feed that arrived wrong. Should this order jump the queue because the customer has been waiting since last Tuesday and already emailed twice. Is the assigned location still the right one now that a week has passed. Those answers move with your season, your margins, and your backlog.

Picture a homeware brand with a warehouse and a 3PL. Forty orders are on hold, most of them short one line. A container clears customs on Thursday and eleven SKUs go positive at the warehouse at once. The rules-engine version fires a scheduled poll, walks the first hundred held fulfillment orders in ID order, and releases whatever passes a store-wide availability check, which quietly includes four orders held for a chargeback review and two that are only coverable at the 3PL. The playbook version reserves against the new receipt, releases twenty-two orders oldest-promise-first with only their inventory hold cleared, partial-ships three where the outstanding line is a coaster set that is still two weeks out, leaves the fraud holds exactly where they were, and sends one message to the eleven customers whose orders had been waiting more than four days.

It also connects to the piece of this that customers already see. Your back in stock alerts go out the second the count moves, which means a shopper who has never paid you anything hears about the restock before the person who paid you nine days ago. When that gap is visible, it reads as a queue you are not running. And if the wait does run long, saying so before they ask is the cheapest ticket you will ever avoid.


The workflow worth building this week

Start with one number, because you almost certainly do not have it. Export every fulfillment order currently on hold with the timestamp of the hold, and for each one find when the missing item last went positive at that fulfillment order's assigned location. The gap between those two columns is what this costs you. Most stores that run it find a median of one to three days and a long tail of orders that have been shippable for over a week.

Then build the alert before you build the release. A twice-daily email listing held orders that are now coverable at their assigned location, sent to whoever owns the fulfillment queue, captures most of the value with zero chance of clearing a fraud hold by accident. Shopify even ships a Flow template for the read-only half of this, a daily summary of on-hold fulfillment orders. Start there. Add the release once you trust the list, and add reserving before you add volume. That is the same compounding logic behind the Shopify automations no one builds: the boring guardrail pays for itself long before the clever feature ships.

Describe it the way you would brief a new fulfillment coordinator on their first morning. When stock lands, look at who has been waiting for it, check it is in the right building, claim the units so nobody double-books them, and let those orders go, oldest promise first. Only lift the hold you put on. Leave anything a person or a fraud check put there. If someone has been waiting nearly a week on a cheap accessory, send the rest of their order today and tell them the last bit is coming. And if the wait ran long, say so once, properly, before they have to ask. That is the brief. The compiler handles the reservations, the hold IDs, the location arithmetic, and the ordering, and your held queue goes back to being a queue instead of a warehouse.

◆ READING Two companion pieces if this one landed: the preorder that holds your order hostage, which is this problem created on purpose at checkout, and the reorder you always make too late, which is why the stock was not there in the first place.

If you are a Shopify merchant with a held orders queue nobody quite owns, a Flow workflow that holds but never releases, or a spreadsheet where you track what is waiting on what, the inbox is open: field-notes@dugong.live. We are collecting case studies for the next issue.