The situation
Gift4Soul had been trading for years on a bespoke PHP platform written by a developer who was long gone. There was no admin export worth the name, no API, and no documentation. The database was reachable, but the product taxonomy lived half in tables and half in hand-maintained PHP includes.
Worse: a large share of the catalogue sat in subcategories that were not linked from anywhere in the navigation. Customers reached them from Google and from old newsletters. A naive crawl of the menu would have silently dropped thousands of products, and nobody would have noticed until the traffic disappeared.
What we did
Reconstructed the catalogue from the outside in. Rather than trust the database dump, we built a crawl-and-parse pipeline that walked the live site, followed pagination, and — critically — probed for category IDs that existed but were never linked. That sweep is what surfaced the hidden subcategories.
Normalised, then diffed. Every scraped product was normalised into a canonical record: handle, title, variants, options, images, category path, price. We then diffed the reconstructed catalogue against the live site’s own product counts, category by category, until the two agreed exactly. The migration only started once that reconciliation was clean.
Rebuilt the structure, not just the rows. 165 collections were recreated with their original hierarchy, and the nested navigation was rebuilt to match — so existing customers and existing Google results landed where they expected to.
Validated after import. Post-import, we re-ran the same counting pass against Shopify. Product totals, collection membership, and menu depth were checked against the source before anyone called it done.
Why it mattered
Most migrations fail quietly. The store goes live, the homepage looks great, and six weeks later somebody notices that a whole product line never made it across — along with the search rankings that pointed at it.
The value here wasn’t the import. It was the reconciliation: proving, with counts, that what went in matched what came out.