4 MIN READ

Shopify to Medusa Migration: The Complete Technical Guide

Catalog, customers, orders, subscriptions and the redirect map that decides whether you keep your rankings. A working plan for moving off Shopify, from people who have done it.

BY RAHUL MEHTAUPDATED
Illustration for “Shopify to Medusa Migration: The Complete Technical Guide” — Shopify

The technically hard parts of a Shopify migration are not the ones teams plan for. Products import in an afternoon. What breaks timelines is subscriptions, historical orders, and the four hundred URLs that used to rank.

This is the plan we actually run.

Phase 0: export everything

Two sources. The Shopify admin exports products, customers and orders as CSV, which is fine for a first look. For a real migration use the Admin API, because CSV drops metafields, variant-level inventory detail and most order sub-records.

export.shbash
curl -sS "https://your-store.myshopify.com/admin/api/2024-10/products.json?limit=250" \
  -H "X-Shopify-Access-Token: $SHOPIFY_TOKEN" \
  -o products.json

Paginate with the Link header until it stops offering rel="next". Pull products, variants, collections, customers, orders, discounts and metafields into raw JSON, and commit that dump somewhere safe. Every later step reads from these files, never from the live store — reproducibility matters when you run the import eleven times.

Phase 1: map the data model

Most of it is a direct translation.

ShopifyMedusaNotes
ProductProductDirect
VariantProduct VariantShopify caps options at 3; Medusa does not
Collection (manual)Product CollectionDirect
Collection (smart)Category or queryRules do not migrate; re-express them
MetafieldModule field or metadataThe decision point — see below
CustomerCustomerPasswords excluded
OrderOrderImport as records, not replayed checkouts
Discount / Price rulePromotionRule syntax differs
MarketRegionRegions carry currency and tax
LocationStock LocationDirect

Metafields deserve real thought. Shopify stores make metafields do everything: spec sheets, badge flags, related products, compliance data. In Medusa you have two options. Low-value display data goes in metadata on the record. Anything you query, validate or build logic against should become a proper field on a custom module with a link to the product. Dumping everything into metadata is the migration decision teams regret at month six.

Phase 2: import the catalog

Write the importer as a Medusa workflow rather than a standalone script, so it runs inside the app with the container available and rolls back cleanly on failure.

src/workflows/import-shopify-products.tsts
import { createWorkflow, WorkflowResponse, transform } from "@medusajs/framework/workflows-sdk"
import { createProductsWorkflow } from "@medusajs/medusa/core-flows"

type ShopifyProduct = {
  handle: string
  title: string
  body_html: string
  variants: { sku: string; price: string; option1: string | null }[]
  options: { name: string; values: string[] }[]
}

export const importShopifyProducts = createWorkflow(
  "import-shopify-products",
  (input: { products: ShopifyProduct[] }) => {
    const payload = transform({ input }, ({ input }) => ({
      products: input.products.map((p) => ({
        title: p.title,
        handle: p.handle,
        description: p.body_html,
        options: p.options.map((o) => ({ title: o.name, values: o.values })),
        variants: p.variants.map((v) => ({
          title: v.option1 ?? p.title,
          sku: v.sku,
          prices: [{ amount: Math.round(parseFloat(v.price) * 100), currency_code: "usd" }],
        })),
      })),
    }))

    const created = createProductsWorkflow.runAsStep({ input: payload })

    return new WorkflowResponse(created)
  }
)

Two rules that save days. Keep the Shopify handle as the Medusa handle — your redirect map depends on it. And batch in chunks of 50 to 100; a 5,000-product single transaction will time out and tell you nothing useful about which record broke.

Phase 3: customers

Import identity and addresses. You cannot import passwords: Shopify stores hashes you never see, and even if you could, they use a different algorithm.

The migration-day flow that works is a forced reset framed as a security upgrade — an email to the full list on cutover day, plus a login page that detects a migrated account with no credential and offers a one-click reset link. Do not silently fail the login; that is where support tickets come from.

Preserve created_at on import. Customer tenure drives segmentation, loyalty tiers and lifetime-value reporting, and it is trivially easy to lose.

Phase 4: historical orders

Import orders as records. Do not push them through the order workflow — you will trigger inventory reservations, payment authorisations and confirmation emails for purchases made two years ago.

Decide the depth deliberately. Two years covers returns windows, support lookups and cohort analysis. Full history is nice for reporting but multiplies import time and rarely earns it. Whatever you choose, keep the Shopify order number in metadata so support can search by the number the customer has in their inbox.

Phase 5: subscriptions

This is the phase that slips. Shopify subscription apps store payment tokens bound to a specific gateway and merchant account. Those tokens are usually not portable, which means:

  1. Ask your PSP whether tokens can be migrated between merchant accounts. Sometimes yes, on the same processor. Usually no.
  2. If no, subscribers must re-enter payment details. Expect to lose some.
  3. Sequence it: run both systems in parallel, migrate cohorts, communicate early, and give people a real incentive to re-authorise.

Build the subscription logic itself in Medusa before migrating anyone — see subscriptions in Medusa. Never cut over subscribers to a system you have not billed a real cycle on.

Phase 6: the redirect map

The highest-ROI hour in the whole project.

Shopify URLMedusa / storefront URL
`/products/<handle>``/products/<handle>`
`/collections/<handle>``/collections/<handle>`
`/collections/<c>/products/<p>``/products/<p>`
`/pages/<handle>``/<handle>`
`/blogs/news/<handle>``/blog/<handle>`

Keeping the handle makes most of this an identity mapping, which is exactly why you kept it. Then:

  • Export every ranking URL from Search Console, not just the ones in your sitemap. Old collection filters and paginated URLs accumulate links.
  • Implement 301s in next.config.js or at the edge — not in application code, which adds latency to every request.
  • Verify the whole map against the live site before DNS changes, then again after.

Storefront SEO for Medusa covers metadata, structured data and sitemaps for the new build.

Phase 7: cutover

A checklist we run, in order:

  1. Freeze catalog edits on Shopify.
  2. Final delta import — products, customers and orders changed since the last run.
  3. Smoke test the full purchase path on production infrastructure with a real card.
  4. Verify tax, shipping rates and inventory counts against the old store.
  5. Switch DNS during your lowest-traffic window.
  6. Submit the new sitemap; watch Search Console coverage daily for two weeks.
  7. Keep Shopify on the lowest plan for 30 days as a read-only reference.

Expect a two-to-four-week ranking dip. That is normal. What is not normal is a dip that does not recover — that is always redirects.


We run these migrations end to end, including the parts nobody enjoys. Get in touch if you want the plan applied to your catalog.

Frequently asked questions

How long does a Shopify to Medusa migration take?

Six to ten weeks for a mid-size store. Roughly: one week of export and mapping, two to three weeks of storefront build, two weeks of data import and validation, one to two weeks of payment and fulfilment integration, and a week of testing and cutover. Subscriptions or heavy B2B logic add three to four weeks.

Will I lose my Google rankings?

Not if you redirect properly. Every URL that receives traffic or links needs a 301 to its new equivalent, in place on launch day. Typical behaviour is a dip for two to four weeks followed by recovery, and stores that improve site speed in the rebuild often end up ahead.

Can I migrate Shopify customer passwords?

No. Shopify does not expose password hashes and uses a different hashing scheme. Plan a forced reset communicated as a security upgrade, with a login page that recognises migrated accounts and offers a one-click reset.

What happens to my Shopify apps?

They do not come with you. Audit them before migrating and sort them into three buckets: replaced by Medusa core functionality, replaced by a direct third-party integration, and genuinely needed as custom development. Most stores find half their apps were compensating for platform limitations that do not exist in Medusa.

Should I migrate historical orders?

Import at least two years, as records rather than replayed checkouts, to cover returns, support lookups and cohort analysis. Keep the original Shopify order number in metadata so support can find orders by the number customers actually have.

Can I run Shopify and Medusa in parallel?

Yes, and for subscriptions you often must. Run Medusa on a subdomain, migrate a cohort, and validate before the full switch. Keep one system authoritative for inventory during any overlap, or you will oversell.

[ Keep reading ]