Migrating products and catalogue data without losing your rankings
Shops worry less about moving the catalogue than about what the move does to their search rankings. In this article I cover both: how products, variants, images, stock, prices and the awkward custom data come across to a custom Solidus store, and then the discipline that keeps rankings, the full URL map, permanent redirects, canonicals, structured data and a crawl before and after. Using an invented Leeds homeware shop with 8,000 SKUs and heavy metafield use, I show where a catalogue move goes wrong and how to make it close to invisible to Google.
The real question behind a catalogue move
When a shop asks me whether it can move to its own store, the catalogue is rarely the worry. Products, prices and pictures feel like something you can copy. The real question, the one that keeps owners awake, is the next one: will the move cost us our search rankings? It is a fair fear. A shop that has spent years earning its place in Google can watch that place evaporate in a weekend if the move is done carelessly.
So this article answers both halves honestly. First, how the whole catalogue moves: products, variants, images, categories, stock, prices and the awkward custom data most people forget. Then the part that actually protects your rankings: the URLs, the redirects, the canonicals and the structured data. The catalogue almost always comes across. Rankings are kept by discipline, not by luck, and I will show you exactly where that discipline goes. This is a spoke of the wider pillar on what moves and what does not, and it pairs closely with the move of your order and purchase history.
One shop, used all the way through
To keep this concrete I use one invented business throughout. Marshlight Interiors (marshlight.example) is a Leeds homeware and lighting retailer. It has about 8,000 SKUs: lamps, shades, furniture and accessories, most of them in several sizes, colours and finishes. Priya runs it. The shop is on Shopify Advanced today and has leaned hard on metafields over four years, so each product carries a material, an assembly time, a delivery class, a lead time and a trade price that none of the standard fields hold. Nobody real, and every figure is invented so I can walk through each step. Marshlight is moving to a custom store built on Solidus, the open source Rails shop framework I build on, which Priya will own outright.
A catalogue is a data model, not a spreadsheet
The first mistake is to picture the catalogue as one big list of products. It is a set of related things, and the migration is a mapping exercise between two data models. It helps to name every part before moving any of it.
A product is the thing a customer thinks they are buying. A variant is the specific version they actually buy: this lamp, in brass, in the large size. The attributes that generate variants are option types (size, colour, finish) with option values (large, brass, matt). Each sellable variant carries its own SKU, its own barcode (a GTIN or EAN), its own price, and often its own weight and dimensions for shipping. Products sit inside categories, which are usually a tree: lighting, then table lamps, then bedside lamps. They also carry a brand or vendor, a tax category, stock levels held at one or more locations, and relationships to other products: related items, cross sells, bundles. On top of all of that sit metafields or custom attributes, the platform specific extra fields that hold real business logic.
Solidus models every one of these cleanly, which is the point of owning the store. A product is a Spree::Product, and every product has a master variant plus any number of sellable variants.1 Option types and option values are first class records, created once and attached to products, and a variant is defined by one value for each option type.2 Prices live in their own records, one per currency, so a multi currency catalogue is native rather than bolted on.3 Categories are taxonomies and taxons: a taxonomy is the tree, a taxon is a branch, and a product can hang under as many taxons as you like through a classification record.4 Stock is tracked as stock items against named stock locations, so two warehouses are not a plugin but a built in idea.3 Free form attributes become product properties.1 The table below is the whole mapping on one page.
| What the old shop calls it | What it is | Where it lands in Solidus |
|---|---|---|
| Product | The sellable concept | Spree::Product with a master variant1 |
| Variant (size, colour) | The specific item bought | Spree::Variant with one value per option type2 |
| Option / attribute | Size, colour, finish | Option type and option value2 |
| SKU and barcode | Stock code and GTIN or EAN | Variant SKU and barcode2 |
| Price, and per currency price | What it costs | Spree::Price, one row per currency3 |
| Category tree | Navigation and grouping | Taxonomy and taxons4 |
| Brand or vendor | Who makes it | A taxonomy, or a product property4 |
| Stock level per location | What is available where | Stock items and stock locations3 |
| Material, lead time, trade price | Custom business data | Product properties or a custom model1 |
The value of the migration is that you fix the shape here. On a rented platform your catalogue is whatever the theme and the apps allowed. On your own store the model is exactly what the business needs, which is part of the wider point about who actually owns your business data.
Getting the catalogue out of the old platform
Every mainstream platform lets you export the catalogue, by a bulk file, an API, or both. Each has an honest edge case, and knowing it up front saves a bad week later.
On Shopify the first tool is the product CSV. It carries the handle, title, description, vendor, product category, type, tags, the three option names and values, the variant SKU, weight, price, inventory quantity, the barcodes column, the image URL and, importantly, the image alt text and the SEO title and description.5 Metafields export too, as columns named in the pattern product.metafields.namespace.key, but there is a sharp limit worth reading twice: variant metafields are not supported in the product CSV at all.5 For those, and for large catalogues generally, the Admin GraphQL API is the real route. Its products query returns products, variants, prices and inventory, is paginated to handle large catalogues, and exposes metafields.6 At 8,000 SKUs with variant level custom data, Marshlight is an API job, not a spreadsheet job. This is the same API first reality I describe for headless and API first Solidus.
On Magento, or Adobe Commerce, the catalogue moves through System, Data Transfer, Import and Export, as CSV. The Add and Update behaviour updates every field except the SKU, which is the stable key, and new categories and new SKUs named in the file are created automatically.7 The honest catch there is the custom attribute set: Magento is an EAV model, so a product can carry dozens of attributes, and each one needs its column in the file and its match in the new store.
On WooCommerce the built in product CSV importer and exporter has shipped since version 3.1.0.8 It handles the core catalogue, and it maps custom fields through a column prefixed meta:.8 The trap is on the way out: by default, no additional meta data will be exported, so you have to tick the custom meta option or add the columns in code, or you will quietly leave the most important fields behind.8
Images and alt text are catalogue data too
Thousands of product images are easy to underestimate because they look like a folder of files. They are not. Each image belongs to a product or a specific variant, each should carry alt text, and the files themselves have to be pulled down and re hosted on the new store rather than left pointing at the old shop's domain, which you are about to switch off.
For Marshlight that is roughly 30,000 image files across 8,000 products. Pulling them is a scripted job against the export or the API: for each product and variant, fetch the image, store it, and record which product and variant it belongs to and in what order. The one thing people drop is the alt text. Alt text is not decoration. It is what a screen reader announces and what Google reads to understand the picture, so it is an accessibility asset and an organic one at the same time. The Shopify CSV carries it in its own column, and the API exposes it, so there is no excuse to lose it.5 Downloadable product files, the manuals and the digital goods, move the same way: fetch, re host, re attach.
Metafields are where a migration is quietly underestimated
If a migration goes over budget, this is usually the reason. The standard fields map in an afternoon. The custom data does not, because it holds the business logic that makes the shop yours, and there is often far more of it than anyone remembers.
Marshlight's metafields are a good example. The material drives a care guide. The delivery class decides the shipping rule. The lead time sets the dispatch promise on the product page. The trade price feeds a separate wholesale view. None of that is in a standard field, and if it is forgotten the new shop looks complete and behaves wrongly. So the work here is a field by field map: every custom field on the old shop in one column, what it means in the next, its type in the third, and where it lands in the new model in the fourth. Some become product properties, some become proper columns on a custom model, some turn out to be dead and are dropped on purpose. This map is dull to make and it is the single highest value document in the whole project. The platforms each hide the data differently, Shopify in metafields, Magento in custom attributes, WooCommerce in post meta, but the discipline is identical: list it, understand it, map it, check it.
The part that actually protects rankings: URLs and redirects
Now the half owners really ask about. Here is the honest headline: most ranking loss in a migration does not come from the move itself. It comes from dropped or wrong redirects. Get this right and rankings travel; get it wrong and no amount of lovely new design will save them.
Every product, category, content page and article on the old shop has a URL, and some of those URLs carry years of accumulated links and ranking signal. Google's own guidance for a move that changes URLs is specific and worth following to the letter: map every old URL to its new one, use server side permanent redirects such as 301 or 308, point each redirect straight at its final destination rather than through a chain of hops, and keep the redirects in place for as long as possible, generally at least one year.9 If you keep the same domain you do not need the Change of Address tool in Search Console, which is only for moving to a different domain or subdomain.9 Google also lets a large site move in sections to test the effect on traffic before committing the whole catalogue.9
The deliverable is unglamorous and decisive: a spreadsheet with a named owner. One row for every old URL that earned a visit or a link in the last twelve months, pulled from Search Console and analytics. Column one, the old URL. Column two, the new one. Column three, the redirect type, 301 in almost every case. Column four, tested, yes or no. At Marshlight's size that is a few thousand rows and two or three days of careful work, and every missing row is a page that returns a 404 after cutover. On the shops I build these redirects live in a database table inside the store with an admin screen, not in a proxy or a DNS service that a future deploy can forget, so they survive every change and the shop's own staff can add one when they spot a stray.
Titles, canonicals, sitemaps and structured data
Redirects are the big lever. Four smaller ones finish the job.
Meta titles and descriptions are part of the catalogue, so carry them across with the products rather than regenerating them, because many were tuned by hand over years. Canonical tags tell Google which URL is the real one when several show near identical content, which happens the moment a product sits under two categories or a filter adds a parameter. Give every page a self referential canonical pointing at its own clean URL, and point variant or filtered URLs at the canonical version so the signals consolidate onto one page.10 The XML sitemap is how Google discovers the new URLs quickly: generate a fresh one from the new catalogue, submit it, and remember the limits, 50,000 URLs and 50 MB uncompressed per file, with a sitemap index pointing at several files once you pass them.11 At 8,000 products plus categories and content, Marshlight fits one file comfortably.
Structured data is the fourth lever, and it is where owning the store pays off, because you control the markup. Product structured data describes the product to Google with its name, image and offer, and marking up which items are variations of one parent helps Google understand a range rather than a pile of near duplicates.12 Within the offer, the price and the three letter currency code are required for a merchant listing, and the GTIN, SKU and availability are recommended and worth providing because each one unlocks a richer result.13 BreadcrumbList markup, which needs the list, each list item, its position and name, tells Google where the page sits in your category tree and shows that trail in the result.14 None of this is a plugin you rent. On your own store it is part of the template.
Multi language and multi currency catalogues
If the shop sells in more than one language or currency, two more things have to carry.
Localised product content, the translated titles, descriptions and metadata, moves as part of the catalogue, one set per language. You then tell Google how the versions relate with hreflang annotations. The rules are strict and easy to get wrong: each version must list every version including itself, the set must be identical on every page, the annotations must be reciprocal or they are ignored, and an x-default entry names the fallback for languages you do not cover.15 Get hreflang wrong and Google may show the German page to a British customer, which reads as a worse result even though the catalogue is perfect.
Prices per currency are the multi currency half. On a rented platform this is often an app with its own rules. In Solidus a price is a record per currency on the variant, so a multi currency catalogue is native, and the migration simply brings each currency's prices across into its own rows.3
The practical path, step by step
Put together, the move follows one disciplined path, and the order matters.
The two crawls are the safety net. A crawl of the live shop is your list of what must still work. A crawl of the staging store, run with the redirect map applied, proves that every one of those URLs resolves to a real page before any customer sees it. The same two crawls after cutover, plus Search Console's coverage report, catch anything the plan missed while there is still time to add a redirect.
Worked example: eight thousand SKUs and heavy metafields
Priya's catalogue comes out through the Admin API, because the variant level trade prices and lead times do not fit the CSV.56 The script records 8,000 products, about 26,000 variants, roughly 30,000 images with their alt text, and eleven metafields per product. The field by field map takes the better part of a week and retires three metafields that turned out to be abandoned experiments.
The redirect map is the quiet hero. From Search Console and analytics the old shop has about 9,400 URLs that earned a visit or a link in the last year: product pages on /products/, category pages on /collections/, articles and a handful of campaign landing pages. Each gets a row and a 301 to its new address.9 Marshlight keeps its domain, so there is no Change of Address step.9 Before cutover, a crawl of the staging store with the map applied finds 140 URLs that still 404, almost all discontinued products that were quietly removed over the years and now point nowhere. Priya decides case by case: most redirect to the parent category, a few to a close replacement. After the fix the staging crawl is clean, every one of the 9,400 resolves.
On cutover morning the domain points at the new store, a fresh sitemap goes to Search Console, and a post cutover crawl confirms zero unexpected 404s.11 Over the following fortnight Priya watches the coverage report, adds four redirects for odd legacy URLs that only showed up once real crawlers arrived, and sees rankings hold. Nothing about that outcome was luck. It was the map and the two crawls.
The honest close
The catalogue almost always moves. Products, variants, prices, images, stock and categories are a mapping exercise, demanding but low risk, and the custom data moves too once you respect how much of it there is. The move is also your chance to fix a model that a theme or an app forced on you, so the new store fits the business rather than the other way round. If you are weighing the whole economics of that, the five year cost of ownership and the platform comparison sit alongside this piece.
Rankings are the part people fear and the part most within your control. They are protected by a full URL map, permanent redirects to the final destination, carried over titles and canonicals, correct structured data, a fresh sitemap, and a crawl before and after that proves nothing was dropped. Do that work and the move is close to invisible to Google. Skip it and you lose months of rankings to a few hundred 404 pages, which is the saddest way to regret a migration, because it was entirely avoidable.
Footnotes
-
Solidus, core/app/models/spree/product.rb (product, master variant, product properties), solidusio/solidus on GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/product.rb ↩ ↩2 ↩3 ↩4
-
Solidus, core/app/models/spree/variant.rb and option_type.rb (variants, option types and values, SKU, barcode), solidusio/solidus on GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/variant.rb ↩ ↩2 ↩3 ↩4
-
Solidus, core/app/models/spree/price.rb and stock_location.rb (per currency prices, stock items and locations), solidusio/solidus on GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/price.rb ↩ ↩2 ↩3 ↩4 ↩5
-
Solidus, core/app/models/spree/taxonomy.rb and taxon.rb (taxonomies and taxons, classification), solidusio/solidus on GitHub, 2026, https://github.com/solidusio/solidus/blob/main/core/app/models/spree/taxonomy.rb ↩ ↩2 ↩3
-
Shopify, Using a CSV file to import and export products (columns, barcodes, image alt text, metafield columns, variant metafields not supported), Shopify Help Centre, 2026, https://help.shopify.com/en/manual/products/import-export/using-csv ↩ ↩2 ↩3 ↩4
-
Shopify, products query, Admin GraphQL API reference (pagination, variants, prices, inventory, metafields), Shopify.dev, 2026, https://shopify.dev/docs/api/admin-graphql/latest/queries/products ↩ ↩2
-
Adobe Commerce, Import data (System, Data Transfer, Import; Add and Update updates all fields except SKU; categories and SKUs created automatically), Adobe Experience League, 2026, https://experienceleague.adobe.com/en/docs/commerce-admin/systems/data-transfer/import/data-import ↩
-
WooCommerce, Product CSV Importer and Exporter (built in since 3.1.0, meta: columns, no additional meta exported by default), woocommerce/woocommerce wiki on GitHub, 2026, https://github.com/woocommerce/woocommerce/wiki/Product-CSV-Importer-&-Exporter ↩ ↩2 ↩3
-
Google Search Central, Site moves with URL changes, Google, 2026, https://developers.google.com/search/docs/crawling-indexing/site-move-with-url-changes ↩ ↩2 ↩3 ↩4 ↩5
-
Google Search Central, How to specify a canonical URL with rel="canonical" and other methods, Google, 2026, https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls ↩
-
Google Search Central, Build and submit a sitemap (50,000 URL and 50 MB limits, sitemap index), Google, 2026, https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap ↩ ↩2
-
Google Search Central, Intro to how structured data markup works and Product structured data, Google, 2026, https://developers.google.com/search/docs/appearance/structured-data/product ↩
-
Google Search Central, Merchant listing (Product) structured data, required and recommended properties, Google, 2026, https://developers.google.com/search/docs/appearance/structured-data/merchant-listing ↩
-
Google Search Central, Breadcrumb (BreadcrumbList) structured data, Google, 2026, https://developers.google.com/search/docs/appearance/structured-data/breadcrumb ↩
-
Google Search Central, Tell Google about localized versions of your page (hreflang), Google, 2026, https://developers.google.com/search/docs/specialty/international/localized-versions ↩