Most failed migrations don't fail on products. They fail three weeks after launch, when a finance person asks why the March GST report is short, or a B2B buyer gets charged tax they've been exempt from for four years. Shopify migration data mapping is the boring spreadsheet work that prevents that, and the fields that cause the damage are almost never products and variants. They're customer tax exemption flags, order notes and cart attributes, refund and partial-refund history, gift card balances, and the HSN codes your invoicing app needs but your old platform stored in a custom attribute nobody documented.
So here is the field-level audit we run before touching an import. Written for people moving from WooCommerce, Magento 2, BigCommerce or a custom PHP build, with the Indian specifics called out.
Map objects before you map fields
The sheet starts with six tabs, not one: products, customers, orders, discounts, content (pages, blogs, redirects), and "nowhere to put this". That last tab is the useful one. Every platform has objects Shopify has no native home for — Magento's customer groups with tier pricing, Woo's subscription objects from a plugin, order-level custom fields from a checkout extension. Deciding early whether each of those becomes a metafield, a tag, a customer segment, a company record on Plus, or gets dropped on purpose is what separates a migration from a data dump.
Then, per field, four columns: source path, Shopify destination, transform rule, and who signs off. If nobody's name is next to a field, that field will be wrong.
Customer tax exemption is the field that gets people sued
Shopify has a per-customer tax exempt boolean and, on Plus with B2B, tax exemption at the company location level. Magento and Woo store the equivalent as a customer group, a tax class, or a plugin field. Nothing about the export makes the mapping obvious, so it gets skipped, and every exempt buyer starts paying tax at checkout.
The reverse error is worse. Import everyone with tax_exempt = true because a developer wanted to stop a validation error, and you under-collect for a quarter. In India this shows up as GST you should have charged on B2B orders and didn't. Reconcile that at year end and it's a real cash cost, not a data cleanup.
Practical rule: export the exempt list separately, get it signed off by whoever owns tax, and import it as a second pass after the main customer load. Never inside the same CSV.
Order notes, cart attributes and the fields your ops team reads daily
Order notes are where the operations knowledge lives. "Call before delivery, gate code 4." "Customer refused first attempt, reship approved by Priya." "COD — verified on WhatsApp." Most migration tools map the order's line items, totals and shipping address, and quietly drop the note field and any custom checkout attributes.
Three related fields go with it:
- Order note → Shopify
noteon the order. Straight mapping, usually available, usually forgotten. - Checkout custom fields (gift message, delivery date, GSTIN captured at checkout) →
note_attributes. Get the key names right the first time, because your fulfilment app or picklist template will be reading those exact keys. - Internal tags → order tags. This is what powers your Flow rules and your "repeat COD refuser" segment later.
GSTIN deserves a line of its own. If B2B customers have been entering it at checkout for two years, that history sits in a custom field and it needs to land somewhere queryable — a customer metafield, not just an order note, so your invoicing app can pull it on the next order.
Refund history: what Shopify can and can't accept
Here's the constraint nobody mentions in a sales call. Historical orders imported through the Admin API come in with manual transactions. They aren't linked to Razorpay, Stripe or your PayPal account, so you cannot refund an imported order through the Shopify admin and have money actually move. Support will refund from the gateway dashboard instead, and the two systems won't agree.
What you can do is bring the refund record across as data so the numbers and the customer history are honest:
- Full refunds → import the order, then create a refund transaction of the same value so lifetime spend is right
- Partial refunds → this is the one that breaks. A ₹4,200 order with a ₹900 partial refund needs the refund allocated against a line item, or your reporting shows the product as fully sold and revenue overstated
- Restocking → decide once whether refunded items count against migrated inventory, and write it down
We usually cap it. Two years of order history with refunds mapped properly, and anything older imported as headline totals only. Nobody's support team is looking up a 2019 partial refund, and paying for that fidelity is a waste of budget better spent on launch-week performance.
Gift cards and store credit are a balance sheet item
Do the arithmetic before deciding this is minor. Say your export shows 1,240 gift cards with a remaining balance, averaging ₹1,850. That's 1,240 × 1,850 = ₹22,94,000 of outstanding liability. Drop those during the shopify migration data mapping stage and you either eat the goodwill cost of reissuing on request, or you don't honour them and spend the next quarter apologising in DMs.
Shopify accepts gift card imports through the API with a balance and an expiry, but not the original code in every case — depending on how the old platform hashed it, you may have to issue new codes and email holders. Plan for that email. Same logic applies to store credit and loyalty points, which usually live inside a third-party app and need a fresh balance import on the Shopify side. Get the loyalty app installed and configured before the customer import, or you'll do the customer load twice.
GST, HSN and the invoice fields that have no owner
Every Indian store has a set of tax fields wedged into whatever slot the old platform allowed. HSN code in a product custom attribute. GST rate as a tax class name. Place of supply derived from a pincode lookup in PHP. Shopify's native model doesn't hold most of that, so it becomes product metafields feeding an invoicing app.
Mapping HSN across a 6,000-SKU catalogue by hand is not happening, so the transform rule matters: source attribute → metafield namespace and key, with a validation pass that flags every blank and every code that isn't the right length. On a first pass we typically find a few hundred SKUs where HSN was never filled in at all, which is a pre-existing problem the migration just exposed. Better now than during a festive-season audit.
Also: weight. Woo stores in whatever unit the shop set, sometimes grams, sometimes kilograms, sometimes both because two people managed the catalogue. Shipping rates and courier manifests run off this. Check the unit before importing, not after 400 orders ship with a rate calculated on 0.5 kg that should have been 500 g.
Timestamps, timezones and the report that's off by 5.5 hours
Shopify stores order timestamps in UTC and renders them in your store timezone. If your export is already in IST and you import it as UTC, every order shifts back five and a half hours. A sale placed at 11:30 PM on 30 September lands on 30 September at 6:00 PM — fine. One placed at 3:00 AM on 1 October lands at 9:30 PM on 30 September, and now your monthly closing numbers don't tie to what finance recorded. Small bug, very annoying conversation.
The related field is published_at on products, which drives "newest first" sorting on collections. Import everything on migration day and every product is equally new, so your new arrivals collection is randomised. Carry the original creation date.
Marketing consent: the field you should be conservative with
Shopify holds email and SMS marketing state with a consent timestamp and a collection source. If your old platform only stored a boolean, you have a subscriber list with no proof of when or how consent was given. Importing it all as subscribed is common and it's how deliverability gets wrecked in week two, because a chunk of that list hasn't opened anything since 2021.
We split it: anyone who has opened or clicked in the last 180 days comes over as subscribed, the rest come over as not subscribed with a tag, and you run a re-permission campaign at your own pace. Slower, and your sender reputation survives the move.
Tags, metafields and product options
Option order is a real trap. Shopify's variant options are positional, and if your source had Colour first and Size second, importing them the other way round reverses the variant picker on the storefront and breaks any hard-coded swatch logic in the theme. Check one three-option product manually before running the full load.
Tags and metafields deserve a decision rather than a default. Anything a customer filters by should be structured — a metafield with a defined type, not a free-text tag with four spellings of "stainless steel". If you're rebuilding collection filtering as part of the move, the data model you set here is what the filtering app reads, so it's worth an hour with whoever merchandises the catalogue. We built FilterPro partly because so many post-migration stores arrive with filter values that were never normalised in the first place.
Redirects are migration data too
A URL map is a field mapping like any other: old path → new path, one row per indexed URL. Pull the list from Google Search Console and your server logs, not from a sitemap, because the sitemap won't include the old URLs that still hold links. Category paths, product paths with IDs in them, CMS pages, paginated collection URLs. Shopify's URL structure differs enough from Magento's and Woo's that a blanket rule won't work; you need a rules layer for patterns plus a manual list for the top few hundred pages by traffic.
Skip this and the traffic drop shows up around day ten and gets blamed on the theme.
How we verify a mapping before trusting it
Three counts and a spot check. Row counts per object, source vs destination. Sum of order totals, source vs destination, to the rupee. Count of customers with a non-blank tax exempt flag, source vs destination. Then pick ten orders at random — one with a partial refund, one COD, one with a discount, one guest checkout, one B2B with GSTIN — and read every field on both sides side by side. On a first pass we find something wrong in about a third of those spot checks, usually a transform rule that worked on the sample data and not on the edge case.
Do the whole thing into a development store first. Imported orders can't be deleted from a live shop, which means one bad run leaves permanent junk in your reporting.
If you're scoping a move now, write the mapping sheet before you get quotes — it makes the quotes comparable, and it surfaces the fields no vendor asked about. Our migration process covers what we hand over, and a free audit is the fastest way to get a second read on your field list before anyone starts importing.

