DEV Community

Sushil Barwal
Sushil Barwal

Posted on Originally published at dzimark.com

WooCommerce to Shopify migration: building the redirect map with a small script

When a WooCommerce store moves to Shopify, products and customers usually move without much trouble. URLs are where you lose traffic. WooCommerce lets the store owner shape permalinks. Shopify doesn't: product URLs always start with /products/ and collections with /collections/. So almost every indexed URL on the old store changes on launch day.

This post walks through how we build the redirect map: collect every old URL, translate the predictable ones with a script, review the rest by hand, then import the result into Shopify as a CSV.

1. Collect every URL that matters

Don't start from the product export alone. You want every URL that Google or a customer might still request:

  • the WooCommerce XML sitemaps (with Yoast or Rank Math that's usually /sitemap_index.xml; WordPress core serves /wp-sitemap.xml)
  • a crawl of the live site
  • top landing pages from Search Console (Performance → Pages, last 12 months)
  • URLs with backlinks, from whatever backlink tool you use
  • existing redirects stored in a WordPress redirect plugin, so you can chain them straight to the final Shopify URL

Put them all in one list and remove duplicates. Keep the query string out unless it actually changes the content.

2. Know the default mappings

Most WooCommerce stores use the default bases. Check Settings → Permalinks in WordPress first, because many stores change them.

WooCommerce URL Shopify URL
/product/blue-hoodie/ /products/blue-hoodie
/product-category/hoodies/ /collections/hoodies
/product-category/clothing/hoodies/ /collections/hoodies (no nesting in Shopify URLs)
/product-tag/sale/ /collections/sale or the closest collection
/2024/05/how-to-wash-wool/ /blogs/news/how-to-wash-wool
/about-us/ /pages/about-us

News is the default blog on Shopify, which is why posts land under /blogs/news/.

3. Translate the predictable ones

Here's the script we start from. It reads a list of old paths (one per line) and writes the two-column CSV that Shopify's URL redirect import expects: Redirect from and Redirect to. Anything it can't map goes to a review file.

import csv
import re
import sys
from urllib.parse import urlparse

# Adjust these to match Settings > Permalinks on the old store
PRODUCT_BASE = "product"
CATEGORY_BASE = "product-category"
TAG_BASE = "product-tag"
BLOG_HANDLE = "news"

KNOWN_PAGES = {"about-us", "contact", "shipping", "returns", "faq"}

def to_shopify(path: str):
    parts = [p for p in path.strip("/").split("/") if p]
    if not parts:
        return None  # homepage stays the homepage
    if parts[0] == PRODUCT_BASE and len(parts) >= 2:
        return f"/products/{parts[-1]}"
    if parts[0] == CATEGORY_BASE and len(parts) >= 2:
        return f"/collections/{parts[-1]}"  # flatten nested categories
    if parts[0] == TAG_BASE and len(parts) >= 2:
        return f"/collections/{parts[-1]}"  # only if you build that collection
    if re.fullmatch(r"\d{4}", parts[0]) and len(parts) >= 3:
        return f"/blogs/{BLOG_HANDLE}/{parts[-1]}"  # /2024/05/slug/
    if len(parts) == 1 and parts[0] in KNOWN_PAGES:
        return f"/pages/{parts[0]}"
    return None

def main(src, out_csv, review_txt):
    rows, review = [], []
    with open(src) as f:
        for line in f:
            url = line.strip()
            if not url:
                continue
            path = urlparse(url).path or "/"
            target = to_shopify(path)
            if target:
                rows.append((path, target))
            elif path != "/":
                review.append(path)
    with open(out_csv, "w", newline="") as f:
        w = csv.writer(f)
        w.writerow(["Redirect from", "Redirect to"])
        w.writerows(sorted(set(rows)))
    with open(review_txt, "w") as f:
        f.write("\n".join(sorted(set(review))))
    print(f"{len(rows)} mapped, {len(review)} to review")

if __name__ == "__main__":
    main(*sys.argv[1:4])
Enter fullscreen mode Exit fullscreen mode

Run it as python map_redirects.py old-urls.txt redirects.csv review.txt.

The script is deliberately dumb. It assumes the product slug stays the same on Shopify, which is the single most useful decision you can make during the migration: if blue-hoodie stays blue-hoodie, most of the map writes itself.

4. Review what the script couldn't map

The review file is where judgement comes in:

  • Flattened categories. Two nested categories with the same last segment (say /men/hoodies/ and /women/hoodies/) can't both become /collections/hoodies. Rename one collection and fix the row.
  • Tag archives. Only redirect a tag to a collection if you actually build that collection. Otherwise pick the nearest real collection.
  • Old campaign pages and attachments. Send them to the closest live page, not the homepage. Mass redirects to the homepage are a common reason for traffic drops after a move.
  • Changed slugs. If the client wants cleaner product handles on Shopify, map those rows by SKU from the product export rather than by URL.

5. Watch Shopify's redirect rules

From Shopify's URL redirect docs, three things trip people up:

  1. A redirect only fires if the old path is broken on Shopify. If something now lives at that path, the redirect is ignored.
  2. Standard plans hold up to 100,000 redirects, which is plenty for most stores.
  3. Some paths can't be redirected at all, including anything starting with /cart, /apps or /shop. WooCommerce's /shop/ page is the usual casualty, so link to /collections/all from the navigation instead.

Import the CSV under Content → Menus → URL redirects → Import, then spot-check a few dozen rows by hand.

6. Test it after launch

Recrawl your original URL list against the live Shopify domain. Every row should return one 301 hop to a page that returns 200. Then submit Shopify's sitemap.xml in Search Console. Because the domain doesn't change, you don't use the Change of Address tool. Google's site-move guidance is to keep redirects for at least a year.

FAQ

Can I keep /product/ URLs on Shopify?
No. Shopify fixes the /products/ and /collections/ prefixes. Keep the slug and redirect the old path.

Should every unmapped URL go to the homepage?
No. Map each one to its closest equivalent. Homepage redirects for unrelated pages tend to be treated like soft 404s.

When do I import the redirects?
Prepare and test them on the unpublished store, then import them before you switch the domain, so they work from the first request.

If you'd rather hand the whole move to a team that does this every month, that's what our Shopify migration service covers, from the URL audit to post-launch checks.

Originally published on the Dzimark blog.

Top comments (0)