Importing Fulfillments
The Fulfillments import ships your orders with tracking from a CSV/Excel file, a Google Sheet, or directly from another connected store. Unlike most imports, fulfillments aren't records you upsert — they're append-only shipments. Each row resolves a real order in your store, finds its open fulfillment orders, and marks them fulfilled with the tracking you supply.
Because an order can be shipped in several parcels (or one row per shipped SKU), the import uses one row per line item — rows that share the same Order belong to the same shipment. A row with no SKU fulfills the whole order's remaining items.
For the wizard itself — uploading, mapping, dry runs, schedules — see Importing into EcomSolo.
Columns
| Column | Notes |
|---|---|
| Order | Required. The order to fulfill, by name (#1001) or ID. This must be a real order in the target store — the import looks it up and reads its open fulfillment orders. Rows sharing an Order are grouped into one shipment. |
| SKU | The variant SKU to fulfill on that order. Must be a real SKU on that order. Omit it to fulfill all of the order's remaining items. |
| Quantity | How many of that SKU to ship. Defaults to the remaining quantity; capped at what's still unfulfilled. |
| Tracking Company | The carrier (e.g. DHL, UPS, FedEx). |
| Tracking Number | The shipment's tracking number. |
| Tracking URL | A direct tracking link (optional — Shopify infers one from the carrier + number for known carriers). |
| Notify Customer | true to email the customer the shipping confirmation. Defaults to false (safe). |
| Location | The location fulfilling the order — only needed to disambiguate an order whose items are stocked at more than one location. Matched by name. |
| Operation | fulfill (default), update_tracking, or cancel. See below. |
Common export headers map automatically: Order Name / Name / # → Order,
Variant SKU → SKU, Qty → Quantity, Carrier → Tracking Company,
Tracking → Tracking Number, Tracking Link → Tracking URL, Notify →
Notify Customer, Location Name → Location, and Command → Operation.
How a shipment is laid out
Each shipment is a group of rows sharing the same Order (an import-time
grouping — the order name itself is sent to Shopify to resolve the order, but
the rows are merged into one fulfillmentCreate call per order per run):
- Whole order — one row with just Order (plus tracking). Every open line item on every open (or in-progress) fulfillment order is shipped.
- Partial / multi-line — several rows with the same Order and different SKU + Quantity. Only those line items ship; the SKU is matched to the order's fulfillment-order line items and capped at the remaining quantity.
Only open and in-progress fulfillment orders are fulfilled. Line items that are on hold, scheduled, or otherwise not yet ready to ship are left untouched, and the report notes the SKU that couldn't be fulfilled.
The Operation column
| Operation | What it does |
|---|---|
| fulfill (default) | Creates a fulfillment against the order's open or in-progress fulfillment orders — whole-order, or SKU-scoped when SKU/Quantity are present. |
| update_tracking | Updates the tracking company / number / URL on an already-created fulfillment for that order (matched by tracking number). Use it to add or correct tracking after the fact. |
| cancel | Cancels a fulfillment on the order. Just give the Order — a plain Order + cancel row cancels the order's most-recent still-cancellable fulfillment (or add a Tracking Number / Fulfillment ID to target a specific one). cancel is permission-gated (it uses the delete-via-import permission). |
Idempotency — safe to re-run
Fulfillments are append-only, so re-running is safe:
- If the order has open fulfillment orders → they're fulfilled (create).
- If the order is already fully fulfilled → the row is skipped and the report notes "already fulfilled" — no duplicate shipment is made.
- Partially-shipped orders top up. Because in-progress fulfillment orders are included, re-running a SKU + Quantity file ships whatever quantity is still outstanding on that order (until it's fully fulfilled, then it skips). Whole-order (no-SKU) rows stay fully idempotent — they never double-ship.
- Mixed done/remaining SKUs. If a row's SKU is already fully fulfilled but another SKU on the same order still has stock to ship, the done SKU is skipped and the remaining one still ships — the order is not failed wholesale. A SKU that isn't on the order at all is still a genuine row error.
- Cancel is idempotent too — re-running the same
cancelfile skips orders whose fulfillment is already cancelled.
The dry run on the Review step shows the create / skip / cancel split before anything is written.
Required Shopify permissions
Creating and cancelling fulfillments uses Shopify's fulfillment-order write
scopes: write_merchant_managed_fulfillment_orders,
write_assigned_fulfillment_orders, and write_third_party_fulfillment_orders.
These are part of the EcomSolo app's install list, but a store connected before
this feature shipped won't have them yet. If a fulfill/cancel run fails with
"This store is missing the Shopify permission …", reconnect the store from
its settings to grant the new scopes, then run the import again. This applies to
the target store for a store-to-store copy as well.
Importing from another store
On the wizard's Source step, pick From another store to copy the source store's fulfillments as import rows — one row per source fulfillment, grouped by Order, with the tracking company, number and URL. Each row fulfills the matching open fulfillment order on the target store, matched by Order name — so both stores must hold the same orders (import your Orders into the target first; the Orders import preserves the source order name, so the names line up). Already-fulfilled orders on the target are skipped, and the target store also needs the fulfillment-order write scopes (see Required Shopify permissions — reconnect it if a run fails on a missing permission).
Results and freshness
Shipments you create take effect in Shopify immediately, and EcomSolo's Fulfillments dashboard (Orders → Fulfillments) refreshes within seconds of the run. The Orders dashboard is refreshed too — each affected order's fulfillment status flips (e.g. Unfulfilled → Fulfilled / Partially fulfilled, or back after a cancel) as part of the same post-run sync, so both dashboards stay in agreement without waiting for the next full sync.
Sample files
Three ready-to-run samples live under features/importing/examples/:
fulfillments-create-sample.csv— the fulfill (create) path only.fulfillments-cancel-sample.csv— the cancel (delete) path only.fulfillments-sample.csv— the all-in-one file covering every case (fulfill + update_tracking + cancel).
Create (fulfillments-create-sample.csv):
Order,SKU,Quantity,Tracking Company,Tracking Number,Tracking URL,Notify Customer,Location,Operation
#1001,,,DHL,JD014600003922222222,https://www.dhl.com/track?id=JD014600003922222222,false,,fulfill
#1002,HAT-BLK-01,1,UPS,1Z999AA10123456784,,true,,fulfill
#1002,SCARF-RED-02,2,UPS,1Z999AA10123456784,,true,,fulfill
#1003,,,,,,false,,fulfill
#1004,,,Canada Post,9247133692471574000,https://www.canadapost.ca/track?id=9247133692471574000,true,Main Warehouse,fulfill
These cover a whole-order fulfill with tracking (#1001), a partial /
multi-line fulfill of two SKU rows on one order (#1002), a minimal
whole-order fulfill with no tracking at all (#1003), and a whole-order fulfill
with an explicit Location for a multi-location order (#1004).
Cancel (fulfillments-cancel-sample.csv):
Order,SKU,Quantity,Tracking Company,Tracking Number,Tracking URL,Notify Customer,Location,Operation
#1001,,,,,,false,,cancel
#1002,,,,,,false,,cancel
cancel needs only the Order (and Operation=cancel) — it cancels the
order's fulfillment. It's permission-gated (the delete-via-import permission).
Swap in real values.
OrderandSKUmust resolve in your target store, so replace#1001–#1004and the sample SKUs (HAT-BLK-01,SCARF-RED-02) with order names and SKUs that actually exist. An order or SKU that can't be found produces a row-level error naming the unresolved value — the rest of the file still runs.
Troubleshooting
| Error in the report | What it means / what to do |
|---|---|
row requires an Order | Every row needs the order name or ID to fulfill. |
order '#1001' not found | No order with that name/ID exists in the target store — check the value. |
SKU 'HAT-BLK-01' isn't on order '#1002' | The order has no open line item with that SKU (already fulfilled, or a typo). |
already fulfilled | The order has no open fulfillment orders left — the row is skipped, not an error. |
FAQ
Will re-importing create duplicate shipments? No — an order with nothing left to fulfill is skipped, so re-runs are safe.
Do customers get emailed? Only if you set Notify Customer to true. It
defaults to false.
Can I fulfill part of an order? Yes — give each SKU its own row with a Quantity; only those line items ship.
Can I cancel a fulfillment? Yes — set Operation to cancel. It's
permission-gated.