Skip to Content

Order mapping

Orders are the heaviest resource. StagingOrder mixes flat fields (addresses split into shipping_address1 etc., a single discount shape) with nested pass-through arrays for line items, transactions, tax lines, and shipping lines.

Quick reference — minimum loadable order

{ "original_id": increment_id, "name": "#" & $string(increment_id), "email": customer_email, "processedAt": created_at, "currency": order_currency, "financialStatus": "PAID", "lineItems": items.{ "title": name, "sku": sku, "quantity": qty_ordered, "priceSet": { "shopMoney": { "amount": $string(price), "currencyCode": $$.order_currency } } } }

processedAt is critical. If you omit it, the order’s “created at” defaults to load time, breaking historical reports. Always emit it from the source’s order timestamp.

Identity & status

namestring

Order display name (e.g. #1042). Customers and merchants see this.

"name": "#" & $string(increment_id)
emailstring

Order contact email — used for notifications. Falls back to the customer’s email if omitted.

phonestring

Order contact phone, and the phone on the customer the order upserts. Shopify only accepts E.164 (+15555550123); a local number is converted for you using billing_country (falling back to shipping_country). Placeholders that hold no number (0, -, blank) are treated as no phone; anything else that can’t be resolved fails validation with INVALID_FORMAT rather than at load time.

customerIdstring

The customer’s id in the source store. Graftport swaps it for the migrated customer, attaching the order to a customer record that already exists.

"customerId": customer_id

Prefer this over the email / firstName / lastName fields wherever the source knows which customer placed the order. Without it, loading an order writes a customer built from whatever the order carries, which overwrites fields the order doesn’t know about. Leave it unmapped for guest checkouts — the order then arrives with no customer attached, which is what a guest checkout is.

Migrate customers before orders, which is the order Graftport runs them in by default. If an order loads before the customer it points at, the order still arrives complete — with its own lines, totals and addresses — but with no customer attached, and re-running won’t attach one later: Shopify lets you change an existing order’s note, tags and shipping address, not who placed it. Include customers in the same migration as orders and this resolves on its own.

discountCodeTextstring

The discount code the customer actually typed. Without it, an order that carries a discount amount gets a generated placeholder code, which is what shows on the order in the admin.

"discountCodeText": coupon_code
processedAtstring (ISO8601)

When the order was placed on the source platform. Always emit this on historical orders so the timeline is preserved.

"processedAt": created_at
closedAtstring (ISO8601)

When the order was closed (typically equal to processedAt for fully-fulfilled historical orders).

financialStatusstring

One of PENDING, AUTHORIZED, PARTIALLY_PAID, PAID, PARTIALLY_REFUNDED, REFUNDED, VOIDED.

"financialStatus": is_paid ? "PAID" : "PENDING"
fulfillmentStatusstring

One of FULFILLED, PARTIALLY_FULFILLED, RESTOCKED. Omit for unfulfilled orders.

taxesIncludedboolean

true if line item prices already include tax (common in EU merchants), false if tax is added on top (common in US).

buyerAcceptsMarketingboolean
sourceIdentifierstring

The original platform’s order ID — preserves cross-system traceability. Often the same as original_id for orders.

sourceNamestring

Where the order originated (e.g. web, pos, magento).

Currency

currencystring

ISO 4217 currency code for the shop’s base currency on this order. Used inside priceSet.shopMoney.currencyCode of every line item.

"currency": order_currency
presentmentCurrencystring

The currency the customer saw at checkout, if multi-currency. Often equal to currency for single-currency orders.

Customer

The order's customer is composed from these fields. The staging model assembles them into Shopify's customer.toUpsert shape.

firstNamestring

Used on the order’s customer record AND on shipping/billing addresses.

lastNamestring
customerNotestring

Note attached to the customer on this order. Different from note (below), which is the order-level admin note.

customerTagsstring[]

Tags attached to the customer (not the order).

Shipping address (flat)

Shopify nests its address shapes; the staging model flattens them. Setting shipping_address1 is what triggers the shipping address being attached.

shipping_address1string
"shipping_address1": shipping.street
shipping_address2string
shipping_citystring
shipping_provincestring

State / province.

shipping_countrystring

Country code (ISO 3166-1 alpha-2 preferred).

shipping_zipstring
shipping_firstNamestring

Recipient’s first name. A gift often ships to someone other than the account holder, so map this when the source keeps a separate delivery name. Left unmapped, the customer’s own name is used.

shipping_lastNamestring
shipping_phonestring

Contact number for the delivery, used by carriers. Normalized against shipping_country.

Billing address (flat)

Same shape as shipping, with a billing_ prefix. Setting billing_address1 triggers the billing address.

billing_address1string
billing_address2string
billing_citystring
billing_provincestring
billing_countrystring
billing_zipstring
billing_firstNamestring

Name the invoice is addressed to — accounts payable, rather than the person who placed the order. Falls back to the customer’s name.

billing_lastNamestring
billing_phonestring

Line items (nested pass-through)

Each line item is a Shopify-shaped object. The staging model validates each one and rejects malformed entries early.

lineItemsLineItem[]required

Array of order line items. Each entry has the same shape Shopify’s OrderCreateLineItemInput accepts:

FieldNotes
titleProduct name as it was at purchase.
quantityint. Required per line.
skuVariant SKU at purchase.
variantIdThe variant’s id in the source store. Graftport swaps it for the migrated variant once the products have loaded. This is what connects the order to the catalogue — without it the line is just a title and a price.
productIdSame, for the product the variant belongs to.
priceSet{ shopMoney: { amount: string, currencyCode } } — money is always strings.
taxLinesPer-line-item tax breakdown: { title, rate, priceSet }.

Anything else on a line item is rejected — map it as an order custom attribute instead.

A line whose variantId or productId names something that hasn’t migrated yet does not hold the order back: the order is created right away with the line kept — title, quantity and price intact — but without the link into your catalogue, and re-running won’t attach the link later because Shopify doesn’t allow an existing order’s lines to be rewired. If you want variant-linked line items, migrate products before orders.

"lineItems": items.{ "title": name, "sku": sku, "quantity": qty_ordered, "priceSet": { "shopMoney": { "amount": $string(price), "currencyCode": $$.order_currency } } }

Discounts (flat — pick one)

Set exactly one discount field. The staging model wires it into Shopify's discount-code shape with a placeholder code.

itemPercentageDiscountnumber

Percentage off as a number 0-100 (25 = 25%). Applied to qualifying items.

"itemPercentageDiscount": discount_percent
itemFixedDiscountnumber

Fixed money amount off (in the order’s currency). Rounded to 2 decimals.

freeShippingDiscountboolean

true if the order had a free-shipping discount.

Fulfillment (flat)

Setting locationId is what triggers the fulfillment record. Without locationId, no fulfillment is created — even if you set the other fields.

locationIdstring

The Shopify location GID this order ships from. Required to create a fulfillment.

"locationId": $$.config.default_location_gid
notifyCustomerboolean

Whether to send a fulfillment notification email when the load creates the fulfillment. Default behavior is Shopify’s (no email for migrated orders is usually right).

trackingCompanystring

Carrier name (e.g. UPS, DHL).

trackingNumberstring
shipmentStatusstring

One of Shopify’s shipment statuses (e.g. DELIVERED, IN_TRANSIT).

Transactions (nested pass-through)

transactionsTransaction[]

Array of payment transactions. Each entry follows Shopify’s OrderCreateOrderTransactionInput shape:

FieldNotes
kindSALE, AUTHORIZATION, CAPTURE, REFUND, VOID, CHANGE.
statusSUCCESS, FAILURE, PENDING, ERROR.
amountSetMoney bag — { shopMoney: { amount, currencyCode } }.
gatewayPayment processor name.
processedAtISO8601.
"transactions": [{ "kind": "SALE", "status": "SUCCESS", "gateway": payment_method, "processedAt": created_at, "amountSet": { "shopMoney": { "amount": $string(grand_total), "currencyCode": order_currency } } }]

Tax & shipping lines (nested pass-through)

taxLinesTaxLine[]

Order-level tax breakdown. Each entry is { title, rate, priceSet }. Order-level tax is only honoured when no line item carries its own, so pick one level and stay there — mixing the two loses tax. If your source breaks tax down per line, put it on the lines (and on the shipping lines) and leave this field out.

shippingLinesShippingLine[]

Shipping methods used on the order. Each entry is { title, code, source, priceSet, taxLines }.

code is the carrier’s rate code and source the rate provider, both shown on the order in the admin. taxLines is the tax charged on the delivery itself — it belongs here rather than on the order whenever the line items carry their own tax.

"shippingLines": [{ "title": shipping_method, "code": shipping_code, "priceSet": { "shopMoney": { "amount": $string(shipping_amount), "currencyCode": order_currency } }, "taxLines": [{ "title": "VAT", "rate": tax_rate, "priceSet": { "shopMoney": { "amount": $string(shipping_tax), "currencyCode": order_currency } } }] }]

Custom attributes & notes

dict_customAttributesobject

Free-form { key, value } map of order-level custom attributes (notes from checkout, gift options, custom forms). Each entry becomes one Shopify custom attribute.

"dict_customAttributes": { "gift_message": gift_message, "preferred_delivery": delivery_window }
notestring

Order-level admin note (separate from customerNote).

tagsstring[] | string

Order-level tags.

Patterns

Magento order — minimum viable

{ "original_id": increment_id, "name": "#" & increment_id, "email": customer_email, "phone": billing_address.telephone, "firstName": customer_firstname, "lastName": customer_lastname, "processedAt": created_at, "currency": order_currency_code, "presentmentCurrency": order_currency_code, "financialStatus": "PAID", "taxesIncluded": false, "shipping_address1": shipping_address.street[0], "shipping_city": shipping_address.city, "shipping_province": shipping_address.region_code, "shipping_country": shipping_address.country_id, "shipping_zip": shipping_address.postcode, "billing_address1": billing_address.street[0], "billing_city": billing_address.city, "billing_country": billing_address.country_id, "billing_zip": billing_address.postcode, "lineItems": items.{ "title": name, "sku": sku, "quantity": qty_ordered, "priceSet": { "shopMoney": { "amount": $string(price), "currencyCode": $$.order_currency_code } } }, "transactions": [{ "kind": "SALE", "status": "SUCCESS", "gateway": payment.method, "processedAt": created_at, "amountSet": { "shopMoney": { "amount": $string(grand_total), "currencyCode": order_currency_code } } }] }

Order with a percentage discount

{ "original_id": increment_id, …, "itemPercentageDiscount": discount_percent }

The staging model wires this into a Shopify discount code with a placeholder code (migration_placeholder_percentage_code) — the discount applies to the order, but no real coupon code is created.

Order with shipping fulfillment

{ "original_id": increment_id, …, "locationId": $$.config.default_location_gid, "trackingCompany": shipment.carrier, "trackingNumber": shipment.tracking_number, "shipmentStatus": shipment.status = "delivered" ? "DELIVERED" : "IN_TRANSIT" }

Capture custom checkout fields

"dict_customAttributes": { "gift_message": gift_message, "delivery_instructions": delivery_notes, "purchase_order": po_number }

Re-loading an order that already exists

Fix a mapping, re-run the load, and the order on Shopify matches — line items included. Getting there takes two different routes, because Shopify only lets a small part of an existing order change in place.

Changed in place (fast, non-destructive):

FieldMapping fields
Emailemail
Phonephone
Shipping addressshipping_address1, shipping_city, shipping_zip, …
Notenote
Tagstags
Custom attributesdict_customAttributes
Metafieldsmetafields

Everything else rebuilds the order. Line items and quantities, prices and totals, tax lines, shipping lines, discounts, transactions, payment and fulfillment status, currency, processed date and the billing address cannot be altered on an order that already exists — so when any of them changes, Graftport rebuilds the order: it creates the corrected order first, then removes the original. If removing the original ever fails, the run still succeeds and the record carries a warning naming the leftover duplicate so you can delete it in the Shopify admin.

A rebuild only happens when one of those fields actually changed. Editing a tag or a note never rebuilds anything. On a same-store migration the comparison is against what your store holds right now, so the very first run already rebuilds exactly the orders your mapping changes — and leaves every other order untouched.

What survives a rebuild: the order number (name), the order date (processedAt), and every field your mapping produces — the new order is built from the same payload a first-time load would use.

What does not: anything added to the order inside Shopify after it was migrated — manual refunds, staff notes, fulfillment changes. The order’s “New order” staff notification also fires again.

Run a dry run first. A dry run reports how many orders would be rebuilt, per resource, without touching Shopify. On a large re-run that number is the thing to sanity-check before you commit.

To keep re-runs strictly non-destructive, use Update specific fields only in the run options and pick from the in-place list above. Orders are then never rebuilt — but line-item and money corrections won’t apply either, and the record says so with a warning naming what it couldn’t change.

Gotchas

Money amounts are strings inside priceSet. amount: 19.99 fails; amount: "19.99" works. Wrap with $string().

  • processedAt is critical for historical orders. Without it, the order shows up as created today.
  • Variants must exist before orders. Run the products load before the orders load — otherwise lineItems can’t resolve to Shopify variant IDs and lose their product link.
  • Discount codes are placeholders. Setting itemPercentageDiscount doesn’t create a real reusable code on Shopify — it applies the discount to this single order with a generated placeholder. Use the discount code resource for real reusable codes.
  • Tax lines at order level vs line-item level: don’t mix. Pick one consistent level per merchant.
  • Currency consistency. Top-level currency and the currencyCode inside every priceSet.shopMoney must match for single-currency orders.
  • shipping_address1 is the trigger — without it, no shipping address is created even if you set city/zip/country.
  • Fulfillment requires locationId. Setting trackingNumber alone produces no fulfillment record.
  • Disable Shopify staff order notifications first. Every loaded order triggers Shopify’s “New order” staff email, which floods inboxes on a historical migration. Turn them off before the load — see Shopify destination → Before migrating orders.
Last updated on