Every migration guide assumes you are coming from a platform with a name. WooCommerce has an exporter. Magento has an exporter. The store a developer wrote in raw PHP in 2016, which has been quietly taking orders ever since, has a MySQL dump and whatever is left of the person who built it.
That store is usually in better commercial shape than anyone expects and worse technical shape than anyone admits. It converts. It also has a products table with a column called extra3 that turns out to hold the fabric composition for exactly one category, populated by hand, since 2019.
This is the method we use to get that store onto Shopify without discovering the missing pieces in week three. It is not glamorous and most of it happens in a spreadsheet before anyone writes a line of code.
Start from the storefront, not the schema
The instinct is to open the database and start mapping tables. Do the opposite. Open the live site and list every distinct thing a customer can see or do — every product type, every filter, every badge, every "notify me", every place a price is displayed differently.
That list is your requirements document. The schema tells you what somebody stored; the storefront tells you what the business actually sells. On one migration the database had a perfectly normalised attributes table that turned out to be entirely unused — the live filters were reading a comma-separated string in a different column. Mapping the tidy table would have produced a store where nothing could be filtered.
Sort every field into one of four buckets
Once you have the storefront list, go through the schema field by field and put each one in a bucket. There are only four, and being honest at this stage is what saves the project.
- Native. It maps directly onto a Shopify concept: title, description, SKU, price, compare-at price, weight, barcode, inventory, images, customer name and address.
- Variant-shaped. It is really an option: size, colour, length, pack count. Shopify allows three options per product. If the store uses four, this is the moment you find out, not the week before launch.
- Metafield. Real data with no native home: fabric composition, care instructions, country of origin, warranty period, HSN code, a supplier reference the warehouse prints on a pick list.
- Dead. Columns nothing reads. There are always more than you expect, and every one you carry across is a field somebody has to maintain forever.
The fourth bucket is the one people skip. A migration is the only moment you can delete a field without a meeting, and a bespoke store that has been running for a decade is usually carrying half a schema of abandoned experiments.
The three-option ceiling is a business decision, not a technical one
Shopify products take three options and a hard cap on variant combinations. Bespoke stores frequently have four or five, because whoever built it had no such constraint and added one whenever a supplier asked.
There is no clever way around this. The choices are real and each has a cost:
- Collapse two options into one. "Size" and "Fit" become "32 Regular", "32 Slim". Clean, works well for apparel, and it multiplies your SKU count.
- Split into separate products. Good when the fourth option is genuinely a different item and bad for the ones a shopper expects to switch between on the page.
- Move it to a line-item property. Right for personalisation — engraving text, gift message — and wrong for anything you need to hold stock against, because a property is not a variant and carries no inventory.
Take this to whoever owns merchandising before you decide. It changes how the store is shopped, and a developer picking on their own will optimise for the import script.
Reconcile the numbers before you trust the export
Write the extraction as a script, not as a series of hand-run queries, and have it print counts at every stage: rows read, rows skipped, rows written, and why each skip happened.
Then reconcile against the live site. Not roughly — exactly. Count of active products, count of variants, sum of inventory, count of customers with at least one order. A custom store almost always has soft-delete flags, so a naive SELECT * resurrects products that were pulled from sale in 2021, and a naive filter on status = 1 silently drops a category where somebody used 2 to mean "active but not in the menu".
When the numbers disagree, the schema is not wrong. Your assumption is.
Customers migrate without passwords, and that needs a plan
You cannot move password hashes into Shopify. Every returning customer will have to set a new password, and if nobody tells them, your support inbox finds out first.
Import customers with their addresses and order history intact, then send a deliberate "your account has moved" email before cutover explaining what to expect and linking to the reset. Treat it as a campaign, not a system notification. The customers who bought most recently are the ones most likely to hit the wall in the first week, so send to them first.
Marketing consent is the other half. A bespoke store's newsletter opt-in is often a boolean with no timestamp and no source, which is not the same thing as consent you can evidence. Import the flag, but do not assume it survives scrutiny.
Order history: how far back is far enough
Historic orders are the part everyone wants and few people need in full. Shopify will take them through the API, but old orders arrive as records rather than as living orders — they do not recalculate, and edge cases in tax or discounts do not always survive.
Ask what the history is for. If it is customer service, two years covers almost every enquiry. If it is accounting, the finance system already has it and the store is not the record. If it is segmentation — lifetime value, repeat-purchase cohorts — you need the totals and dates, not every line item.
Deciding this early usually shrinks the job by a factor of five.
URLs are the part that costs money if you get it wrong
A bespoke store has bespoke URLs, and they will not match Shopify's /products/ and /collections/ structure. Every one of them that has a link or a ranking needs a 301.
Export the full URL list from your server logs and Search Console rather than from the database — the database knows what exists, the logs know what is actually being requested, including the ten-year-old landing page nobody remembers that still earns traffic. Map old to new as a file, keep it in version control, and test it against the staging store before DNS moves.
We have written a fuller Shopify migration guide covering the redirect and indexation side, and it is worth reading alongside this one because the database work and the SEO work fail in different ways: a bad field mapping is visible immediately, and a bad redirect map is invisible for six weeks and then shows up as a traffic chart.
Run both stores in parallel before you switch
Do not cut over on the strength of a passing import. Put the Shopify store on a password-protected staging domain and have the people who use the current store every day — the merchandiser, the warehouse, the person who answers the phone — do their actual jobs in it for a week.
They find things a test plan does not: the report they run every Monday, the SKU format the courier's system expects, the fact that one supplier's products need a different despatch note. None of this is in the schema. All of it stops the business if it is missing on day one.
What this usually costs in time
The extraction script is rarely the long pole. The long pole is the decisions — three options versus four, how much order history, which fields are genuinely dead — because each one needs somebody with authority to answer and they are not sitting waiting for the question.
Start those conversations in week one, in parallel with the technical discovery. A migration that stalls almost never stalls on code.
If you are staring at a database somebody else designed and trying to work out whether this is a four-week job or a four-month one, send us the store and we will tell you what we would want to know before quoting it. We do this work often enough to have seen most of the ways a bespoke schema hides its surprises, and you can also bring in a developer for the extraction alone if the rest of the build is already covered.

