Model Shopify Partial Fulfillment by Line Item

A line-item fulfillment model prevents split shipments, moves, cancellations, and retries from closing Shopify orders or triggering customer actions too early.

A customer orders three products. Two ship from one location today; the third moves to another location and leaves next week. If your Shopify partial fulfillment automation stores one status for the whole order, it can send the wrong delivery email, close a support task too early, or mark revenue as operationally complete while one item is still waiting.

A reliable model is smaller and more precise. Keep the order as the commercial container, then track fulfillment at the line-item quantity level. Below is the state record, update sequence, and failure test suite needed to do that without turning a straightforward integration into a warehouse-management project.

Order status is a projection, not the source of truth

Shopify's own data model points in this direction. An order can have multiple fulfillment orders, and each fulfillment order groups line items that are processed together. Shopify explicitly notes that this lets an order be processed in parts. A fulfillment then represents work completed for one or more of those items. See the official Order and Fulfillment documentation.

Three clocks may be running at once: what the customer purchased, what a location has been assigned to handle, and what has actually shipped. A field such as order_fulfillment_status compresses them into a summary. That works for display and filtering. It is too lossy to drive every downstream action.

One failure pattern is to fire business actions on a transition to "partial." The first parcel triggers a shipping message, a CRM stage change, and a support-task closure. When the remaining item moves, is held, or ships later, the automation has no durable record of which quantity caused each action. Retrying the workflow is risky because the order-level status has not changed, even though the underlying fulfillment work has.

Store the unit that can actually change

The first version does not need a copy of Shopify. It needs a compact operational ledger keyed below the order level. Use stable Shopify IDs, not SKU or product title, because catalog labels can change and the same SKU can appear in edge cases your automation should not have to interpret.

{
  "shop_id": "...",
  "order_id": "...",
  "fulfillment_order_id": "...",
  "fulfillment_order_line_item_id": "...",
  "order_line_item_id": "...",
  "assigned_location_id": "...",
  "ordered_quantity": 3,
  "fulfilled_quantity": 2,
  "cancelled_or_refunded_quantity": 0,
  "open_quantity": 1,
  "last_observed_at": "2026-08-26T01:15:00Z"
}

This is a projection your system owns, not a replacement for Shopify. The important design choice is that quantities are explicit. Shopify's fulfillment creation inputs identify fulfillment-order line items and the quantity to fulfill. Shopify also documents that a partial fulfillment request can split the original fulfillment order into submitted and remaining groups. Your local record must therefore tolerate a line item's work moving between fulfillment-order IDs while preserving the connection to the original order line.

Keep the source event ID or webhook receipt separately for duplicate detection. Do not make it the primary key of the business record. One webhook delivery describes an observation; the ledger represents current operational truth.

Recompute downstream actions from current state

Process each relevant webhook as a signal to reconcile, not as a command to send an email. Persist the receipt, fetch or read the affected fulfillment state, upsert the line-level projection, then evaluate business rules against the newly committed state. Shopify supports fulfillment-order webhooks for domain events, but the event that wakes the workflow and the state that authorizes a side effect are different things.

A practical rule table might look like this:

ActionRequired stateDeduplication key
Send shipment emailA new fulfillment with tracking existsfulfillment_id + tracking_number
Mark order fully shippedOpen shippable quantity equals zeroorder_id + fully_shipped
Create exception taskOpen quantity remains and its fulfillment order is on hold or rejectedfulfillment_order_id + exception_type
Close shipping support taskEvery shippable line is complete or removedorder_id + shipping_task_closed

Write the side-effect key in the same transaction as the decision whenever your database supports it. If the email provider or CRM call fails afterward, mark the effect as failed and retry that effect. Do not rerun the whole order workflow and hope every downstream system deduplicates it for you.

Test the events that reshape an order

A test with one item and one location misses the hard cases. The useful acceptance suite changes the grouping or quantity after the order exists:

ScenarioExpected invariant
Two of three units shipOpen quantity is one; the order is not fully shipped
Remaining unit moves to another locationAssignment changes without creating a second customer shipment notice
One line is removed in an order editCompletion uses the current required quantity, not the original total
A fulfillment is cancelled and recreatedShipped quantity is not counted twice
The same webhook is delivered twiceLedger state and side-effect count remain unchanged
Events arrive out of orderA later reconciliation cannot be overwritten by stale observed state

Reassignment deserves special attention. Shopify's FulfillmentOrder documentation explains that moving only a subset of line items can close an existing fulfillment order and recreate line items in another one. A design keyed only by fulfillment_order_id can interpret that as deleted work plus brand-new work. Retain the order-line relationship, compare quantities, and make replacement explicit.

Build the smallest reliable version

For an SMB integration, start with four parts: a webhook receipt table, the line-level fulfillment projection, a side-effect ledger, and a scheduled reconciliation job for recently active orders. Event sourcing, a streaming platform, and a general warehouse orchestration layer can wait until the operating volume or recovery requirements justify them.

The scheduled reconciliation is what keeps the design honest. Webhooks make updates fast; a periodic read repairs missed, delayed, or misinterpreted events. Limit the job to orders with recent activity or open quantity so the recovery path stays inexpensive. Alert only when the source state and local projection still disagree after a retry window.

If your current automation uses one order-level status to trigger customer messages, CRM changes, and operational closure, audit those three actions first. PASMO can help map the fulfillment state, isolate side effects, and build the smallest recovery loop that fits your Shopify and fulfillment stack.