Skip to content

Magento 1 to Shopify: a weekend port without losing SEO

A 14-year-old Magento 1 shop, two days, three people, one rule: no URL changes. Here is the actual migration plan we ran, plus the part that nearly broke us.

Jacob Molkenboer
Jacob Molkenboer
Founder · A Brand New Company
Published
25 May 2026
Reading time
8 min read
Category
E-commerce
Brass bell, linen-tied parcel with green wax seal, brass shipping tag, red stamp on forest linen runner.

Friday, 16:40 Amsterdam. A founder forwards us an email from her hosting provider. The shared box running her Magento 1.9 webshop has been flagged for "abnormal outbound traffic" and will be suspended Monday 09:00 unless the shop migrates or the install is hardened. The store does roughly €1.4M a year in B2B orders and ranks page-one for thirty-eight product-category terms. She has the weekend.

We have had this call four or five times a year since 2020. Magento 1 reached end-of-life on June 30, 2020. Adobe stopped shipping security patches that day. Community forks (OpenMage, mostly) keep the lights on for shops that bothered to migrate the platform itself, but most stores we inherit are running stock Magento 1.9.x with a tower of third-party modules from 2014, half sold by vendors whose Marketplace accounts are now deleted.

This post is the actual weekend timeline. What we did, what we cut, what we almost missed, and the one part of the import that nearly cost the client a quarter of her rankings.

The Friday triage

Before we touched anything, we needed five things. Total SKU count and category depth, the live URL inventory (not just the sitemap, the actual crawl), order history retention requirements, the payment provider, and how customer-group pricing was wired into the front end.

The crawl was the most important. We ran Screaming Frog from 17:00 to 17:25. The shop had 2,143 indexable URLs: 1,847 product pages, 142 category pages, 121 CMS pages, and the rest a long tail of layered-navigation variants that should have been canonicalised years ago and never were. That layered-navigation tail is where most weekend migrations bleed traffic. If you let those URLs 404, Search Console fills with errors, the crawler pulls back, and the canonical product pages get crawled less frequently for two to three weeks. So the rule was: every URL on that crawl gets a 301, even the junk ones, and the junk ones go to the parent category.

For the product, customer and order data we exported through the Magento admin (Reports refresh, then catalog/product and customer/customer exports). For the catalog images we rsynced the entire /media/catalog/product directory off the server in the background while we kept working. That rsync ended up being the slowest single step of the weekend, almost four hours for 38GB of product photos accumulated since 2012.

Why Magento 1 is the line in the sand

The argument we have with founders running Magento 1 in 2026 is always the same. "It works. We get orders." It does work, until the day it doesn't, and the day it doesn't is usually the day a Magecart-style skimmer gets injected into the checkout because a 2017 payment module has an unpatched XSS hole. Adobe held the EOL date in 2020 and nothing has shipped from them since. The PHP version most of these shops run on (5.6 or 7.0) has itself been out of support for years.

It is also the supply chain. Every PHP module in a typical Magento 1 install is a dependency that has not been audited since 2020 at best. Some of those module authors have deleted their accounts; some have been sold on. The original Magecart waves specifically targeted Magento checkout modules and that risk has not gone anywhere, it has just aged. Every module is a door, and the people who built those doors have moved on.

The 301 map is the whole job

If you take one thing from this post: the 301 redirect map is not part of the migration, it is the migration. Everything else is product entry. Shopify URL structure is fixed. Products live at /products/{handle}, collections at /collections/{handle}, pages at /pages/{handle}. Magento gave you anything, with rewrites and trailing slashes and URL keys per store view. You have to map old to new, deterministically, before you import a single product.

We wrote a small Node script that took the Screaming Frog CSV plus the Magento catalog export and emitted a Shopify-compatible redirects CSV. The handle-generation logic matters: Shopify slugifies aggressively, so a URL key like premium-leather-sofa becomes /products/premium-leather-sofa cleanly, but office-chair-(deluxe) becomes /products/office-chair-deluxe and the parentheses get dropped. If you do not pre-compute the handle on your side and let Shopify generate it on import, you will spend Sunday afternoon hand-fixing the redirects file.

function shopifyHandle(magentoUrlKey) {
  return magentoUrlKey
    .toLowerCase()
    .normalize('NFKD')
    .replace(/[\u0300-\u036f]/g, '')   // strip diacritics
    .replace(/[^a-z0-9]+/g, '-')        // non-alphanumeric to dash
    .replace(/^-+|-+$/g, '')            // trim leading and trailing dashes
    .slice(0, 255);
}

That function has to match Shopify's internal slugifier exactly. We tested it against a sample of 200 handles by creating a draft product through the Admin API and reading back the generated handle. Once verified, you can pre-compute every handle, write them into the product import CSV, and write the matching 301 map in the same loop.

The redirects file goes into Shopify via the URL Redirects import screen under Online Store, Navigation. Shopify accepts a CSV with two columns: from path and to path. The hard cap is 100,000 redirects per shop, which sounds like a lot until you remember layered-navigation tail URLs.

For the SEO theory behind the move, Google's site move guide is still the canonical reference and is worth reading the night before you cut over. The short version: 301s (not 302s), keep them live for at least 180 days, resubmit the sitemap the moment the new URLs are reachable.

Product import gotchas

Shopify's product CSV is forgiving on the easy stuff (title, vendor, body HTML, image URLs) and punishing on the rest. Three traps caught us this weekend.

Configurable products become variants. A Magento configurable product with three colour options and four sizes is one Shopify product with twelve variants. The CSV layout for that is one row per variant, with the parent product fields filled only on the first row. If you forget the empty parent fields on rows two through twelve, the importer creates twelve separate products. We had this happen once in 2022 and have not forgotten.

HTML in descriptions gets sanitised. Magento let you put anything into a product description, including iframes, inline scripts, and the kind of nested table layout that came out of FrontPage in 2003. Shopify strips most of it on import. If you have product descriptions with embedded video tutorials or dimensioned table layouts, run a script that converts them to clean semantic HTML before import. We use a small DOMPurify pass with a custom allowlist.

Image URLs must be publicly reachable during import. Shopify fetches the image URL you supply, downloads it, hosts it itself, then discards the source. If you point the CSV at the old Magento media URL and the old server is firewalled or already taken down, you import a catalogue with no images. The fix is to push all images to a temporary S3 bucket or the Shopify Files endpoint first, then reference those URLs in the CSV.

The B2B price list problem

This is the bit that nearly broke us. Magento 1 has customer groups baked in: you mark an account as "Trade", you assign a "Trade" price to a product, that customer sees the trade price. Shopify, on the standard plan, does not have this natively. You need Shopify Plus with the B2B catalogues feature, or you stitch it together with an app plus metafields.

For this client, the trade tier was about 22% of revenue and used by 340 verified accounts. Losing it for a week was not an option. We pre-provisioned a Shopify Plus B2B catalogue with the trade pricing as a CSV import, mapped the 340 accounts as B2B customers with the catalogue assigned, and shipped a logged-in test for three accounts before the cutover. The B2B layer took longer to set up than the product import did.

Warning

If your Magento shop uses customer-group pricing for more than 15% of revenue, decide on your Shopify B2B approach before you quote the migration. Retrofitting it post-launch is a much bigger job than building it in.

DNS, SSL, and the cutover

We dropped the DNS TTL on the apex and www records to 300 seconds on Saturday at 10:00. That gave us until Sunday morning for the change to propagate through the long-cached resolvers. The actual cutover was Sunday 14:00 Amsterdam. We pointed the records at Shopify, waited 90 seconds, and ran the post-cutover checklist.

The checklist is short and brutal. Hit the homepage. Hit ten product URLs at random from the old crawl, verify each 301s correctly. Place a real order using a real card. Trigger a password reset and confirm the email arrives from the right sender. Submit the new sitemap to Search Console. Verify the robots.txt is not blocking anything (Shopify's default is sensible, but if you had custom rules in the old robots.txt, copy them into the theme's robots.txt.liquid).

The Shopify SSL was live within seven minutes of DNS propagation. That part is the smoothest piece of the platform.

The Monday morning numbers

By Monday 08:30 the shop was live, the sitemap was submitted, and Search Console showed the new URLs being discovered. Over the next ten days, organic traffic dipped about 12% week-over-week (most of it the layered-navigation tail dropping out of the index, which we wanted), then recovered to baseline by day 11. The 38 ranking head terms held position throughout. We never dropped below position 4 on any of them.

Two soft losses worth flagging. The product review count reset from "412 reviews" to "0 reviews" because the old shop used a self-hosted Magento module that we could not export cleanly. We re-imported the historical reviews through Judge.me a week later. And one product line had its meta descriptions overwritten by Shopify's default template because we forgot to map the meta_description column in the import CSV. That was fixed in twenty minutes with a bulk edit.

Takeaway

A Magento 1 to Shopify port in a weekend is feasible. The 301 map is the whole job, the B2B pricing layer is the hidden trap, and you keep your rankings by treating every URL in the old crawl as load-bearing.

What we would do differently

If we ran this weekend again, we would push the catalogue image rsync to Thursday night, not Friday evening. Four hours of bandwidth on the critical path was the single largest schedule risk we took. We would also write the B2B catalogue assignment script (the one that mapped 340 customers to the trade catalogue) before Friday, not on Saturday afternoon. Everything else was paced about right.

When we ran this legacy migration for the B2B distributor, the thing we almost missed was the layered-navigation tail in the redirect map. We solved it by treating every URL in the Screaming Frog crawl as load-bearing until proven otherwise. If you are staring at a Magento 1 admin panel and a hosting deadline, the smallest useful thing you can do today is run a crawl of your current shop and count the URLs. That number is the size of your migration, and it is almost always larger than the founder thinks.

Frequently asked

How long does a Magento 1 to Shopify migration actually take?+
A weekend is the minimum for a small shop with clean data. Plan two weeks if you have 10,000+ SKUs, customer-group pricing, or custom modules with no off-the-shelf Shopify equivalent.
Will I lose my Google rankings during the move?+
Not if you preserve every URL with a 301 redirect, keep the sitemap fresh, and resubmit it through Search Console immediately after cutover. Most ranking loss in these migrations comes from skipping the redirect map.
Is Shopify Plus required for B2B pricing?+
For native customer-group style pricing, yes, Shopify Plus with B2B catalogues is the cleanest path. The alternative is an app plus metafields, which works for small trade segments but gets fragile fast.
What happens to my order history and customer accounts?+
Customers can be imported via CSV but they must reset passwords (Shopify cannot import hashed passwords). Order history can be imported via the Bulk Operations API or kept read-only on a subdomain for reference.

Want to build something similar?

Send us one paragraph about the process that eats the most of your week. We'll reply with an honest plan — within 4h on weekdays.