1
6 Comments

I just launched a Shopify-to-WooCommerce migration plugin on WordPress.. 78 downloads in 2 days, 0 sales, 1 real client (me). Here's the

Every few months I see the same thread on freelance forums: "My client wants to move from Shopify to WooCommerce. Is there a plugin for that?"

The answers are always the same: a few outdated paid tools with mixed reviews, some CSV workarounds, or "just do it manually." Nobody seems to have built the obvious thing — a plugin that talks directly to the Shopify API and imports everything cleanly into WooCommerce.

So I built it.

The itch
I'm a developer at La Terreta Games, and one of our clients needed to migrate a ~400-product Shopify store to WooCommerce. Variable products, multiple languages, collections as categories. The existing tools either cost $200 one-time (for something you use once), were last updated in 2021, or simply didn't handle variants properly.

I spent a weekend writing a custom script. It worked. Then another client needed the same thing. Then another.

At that point I had two choices: keep copy-pasting a script, or build it properly and put it on WordPress.org.

I chose the latter, mostly because I was curious about the WP.org ecosystem and whether a niche tool like this could actually get traction organically.

Building it
The core is straightforward: connect to the Shopify Storefront API via GraphQL, pull products and collections with cursor pagination, create WooCommerce products via the WC data layer.

The interesting parts were the edge cases:

Variable products — Shopify variants map reasonably well to WooCommerce variations, but the attribute system is completely different. WooCommerce wants registered attributes and terms; Shopify just has free-form option names and values. I had to build a mapping layer.

Multilingual — Many migrating stores have EN/ES/DE/FR translations stored in Shopify metafields. I fetch those automatically and store them as product meta so any frontend or translation plugin can consume them.

The "already imported" problem — If you run the migration twice, you don't want duplicate products. I store the Shopify product ID as _shopify_id post meta and skip (or update, depending on settings) anything already in the database.

The free version handles all of this. No artificial caps on product count, no paywalled core functionality.

The WordPress.org compliance rabbit hole
This is where it gets interesting for anyone considering the WP.org free + Pro model.

WordPress.org has a strict policy against "trialware" — you cannot ship a plugin to their repository with features that are locked behind a license check. The free build must work as-is, without any gated code.

That means no if (is_pro()) { ... } wrappers. Pro-only code must be physically absent from the free ZIP.

My solution: hooks as a bridge.

The main plugin fires apply_filters('lmsf_graphql_product_extra_fields', '') and do_action('lmsf_after_product_imported', $postId, $product). In the free build, those return empty defaults. The Pro build includes extra files that register callbacks on those hooks.

// Free build: this filter returns '' — nothing added to the query
// Pro build: images.php registers a callback that returns image fields
apply_filters('lmsf_graphql_product_extra_fields', '')
Two separate ZIPs from the same source. The free one goes to WP.org SVN. The Pro one goes to our WooCommerce product download on our site. Clean compliance, no duplicated logic.

Getting this architecture right took longer than building the actual features.

Current Pro features
After [X months] of iteration, Pro includes:

Image download — product and category images fetched from Shopify CDN and stored in the WP media library
Scheduled sync — keeps prices, stock and availability in sync with Shopify on an hourly/daily/weekly cron
Custom metafields — map any Shopify metafield (namespace + key) to a WooCommerce product meta key
Tag mapping — map Shopify product tags to any WooCommerce taxonomy (product_tag, pa_material, whatever you have registered) — shipping next week in v1.0.8
Numbers (day 2)
I published the plugin to WordPress.org two days ago. Here's where things stand:

  • 78 downloads from the WP.org directory — no marketing, no posts, purely organic search
  • Active installs: <10 — WP.org rounds down aggressively at this stage; the counter only updates weekly and requires confirmed pings from live sites
  • Pro sales: 0 — I launched the Pro version simultaneously but haven't promoted it anywhere yet. This post is the first time I'm telling anyone it exists
  • Real-world test: 1 — my own store, which migrated cleanly with
    9 products, more than 20 variants and multilingual translations
    78 downloads in 48 hours for a niche B2B tool with zero marketing felt like a signal worth paying attention to. There are clearly people searching for this. Whether any of them convert to Pro is the next question.

What's surprised me
The niche is real. Shopify-to-WooCommerce migration is not a huge market, but the people who need it really need it. They're motivated buyers. I've had people reach out within hours of finding the plugin asking about Pro features.

WP.org organic discovery actually works. I didn't do any marketing for the first week. Installs came purely from the plugin directory search. The SEO value of a WP.org listing is underrated.

The compliance work paid off. A few plugin authors I talked to had their plugins suspended for trialware violations. Spending the extra time to do the hook-based architecture properly meant I never had that problem.

Support is a product feature. Every time someone files a bug about an edge case (a weird variant structure, a Shopify store using an unusual API version), fixing it improves the plugin for everyone. I've stopped thinking of support as a cost.

What's hard
The Shopify Storefront API has limitations. It's designed for storefronts, not migrations. Some data (order history, customer accounts, some metafield types) isn't accessible without the Admin API, which requires a different app setup and more permissions. I'm navigating how to offer an "Advanced" tier for that without over-complicating the onboarding.

Pricing the Pro version. I went with a simple lifetime licence because it felt honest for a tool people use once or twice. But it means no recurring revenue on existing customers. I'm reconsidering adding an annual option for customers who want to keep the scheduled sync running long-term.

Building in public is awkward when the product is B2B niche. Nobody on Twitter cares about WooCommerce migration plugins. The IH community is honestly the right place for this.

What's next
Roadmap for the rest of 2026:

v1.1 (June) — Sync dashboard with change diff (price before/after, stock before/after, per product)
v1.2 (August) — 301 redirect generator (auto-map old Shopify URLs to WooCommerce URLs, export for .htaccess or Redirection plugin)
v1.3 (September) — Native WPML/Polylang integration (create translated post duplicates, not just meta)
v1.4 (November) — Real-time sync via Shopify webhooks
v1.5 (December) — Product review import from Judge.me / Loox
Would I do it again?
Yes, but I'd skip the "let me just write a quick script" phase and go straight to the plugin architecture. The incremental refactoring from "script" to "proper plugin" cost me [X hours] that I could have avoided.

I'd also have set up the Pro/free split from day one instead of retrofitting it after the first version was on WP.org.

If you're running a WooCommerce store (or know someone who is) and have been putting off the Shopify migration because it feels complicated — the free version is on WordPress.org. Happy to answer any questions.

And if you've built something in the WordPress/WooCommerce ecosystem, I'd love to hear how you handle the freemium compliance side — it's an undertalked problem.

posted to Icon for group Share Your Project
Share Your Project
on May 7, 2026
  1. 1

    Using a hook-based architecture to navigate WP.org compliance is a clever way to keep your Pro code private and your codebase clean. Those 78 organic downloads prove that people are tired of manual CSV workarounds and are actively searching for a real fix.
    It is great to see a niche tool get immediate traction just by being the one thing that actually works as promised. You turned a repetitive developer headache into a professional bridge for store owners who were stuck in transition.
    What is the most common "edge case" variant structure that almost broke your mapping logic?

    1. 1

      Great question — the one that caught me most off guard was Shopify's "Default Title" phantom variant.

      In Shopify, every product has at least one variant — even simple products with no options. The API returns them with a single variant called "Default Title" and an option Title: Default Title. If you treat that naively as a variable product, WooCommerce creates a product with one meaningless attribute and one variation, which breaks pricing and looks terrible in the storefront.

      The fix was detecting that specific pattern before deciding product type:

      $is_default_variant = count($variants) === 1
      && ($variants[0]['title'] ?? '') === 'Default Title';

      $type = $is_default_variant ? 'simple' : 'variable';
      Simple once you know it's there, but it affects every simple product in Shopify so getting it wrong breaks the majority of imports.

      The second one worth mentioning: option names with special characters. WooCommerce slugifies attribute names via sanitize_title(), so an option called "Talla/Size" becomes talla-size as a slug but the variation still tries to match against the original string. That one showed up in a bilingual Spanish/English store and took a while to track down.

      1. 1

        Those hidden ghost variants are like invisible traps that can ruin a storefront if you are not careful. Your fix is a perfect example of why a specialized tool is so much better than a generic script. Solving the bilingual naming issue shows you are really paying attention to the messy details that matter.

        Was there any specific data point that was surprisingly difficult to fetch through the GraphQL API?

        1. 1

          Metafields, without a doubt.

          The Storefront API doesn't let you fetch "all metafields" on a product. You have to know the exact namespace and key in advance and request each one explicitly using GraphQL aliases:

          title_es: metafield(namespace:"translations", key:"title_es") { value }
          material: metafield(namespace:"custom", key:"material") { value }
          Which means there's no discovery — you can't ask the API "what metafields does this product have?" You have to know what you're looking for before you query. The Admin API has a proper metafields connection with pagination. The Storefront API treats them as individual named fields only.

          On top of that, each metafield has to be explicitly enabled for Storefront API access in the Shopify app settings. If the merchant forgot to toggle a metafield namespace, the field silently returns null — no error, no warning, just missing data.

          The workaround I built: a mapping table where the user defines which namespaces and keys they want to import. It turns a limitation into a feature — explicit control over what gets pulled — but it took a while to realize that was the right design rather than fighting the API.

          The other one that surprised me: sale price detection. WooCommerce has separate regular_price and sale_price fields. Shopify's Storefront API has no sale price concept — you detect it by checking if compareAtPrice > price on each variant. compareAtPrice is null when there's no sale, returns an object with amount when there is. Simple once you know the pattern, but it's not documented prominently.

          1. 1

            Shopify’s metafields sound like a frustrating guessing game since you cannot just list them all. Turning that limitation into a mapping feature is a great way to give users control instead of fighting the API. The sale price logic feels like a classic hidden rule that every developer has to learn the hard way.
            Did you find any other API quirks that felt more like a riddle than documentation?

            1. 1

              A few more, yes.
              Stock quantity is a subtle one. The Storefront API only exposes availableForSale (boolean) and quantityAvailable (integer, but only for online-fulfillment channels and not always present). WooCommerce wants a real stock number to enable manage_stock. If quantityAvailable is null — which happens when the merchant hasn't set inventory tracking in Shopify — you have to decide: import as instock with no quantity, or outofstock, or skip stock management entirely. I ended up defaulting to instock with a flag on the product meta so users know the stock wasn't synced. Silent data loss is worse than a visible warning.
              Collection membership hits you when you approach it from the product side. The Storefront API has no product.collections connection. You can't ask "which collections does this product belong to?" You have to invert the whole approach — query each collection, get its products, and build a reverse map. On a store with 50 collections and 400 products that's 50 paginated requests instead of one. The Admin API handles this cleanly; the Storefront API just wasn't designed for it.
              Weight units are inconsistent per variant. weightUnit can be KILOGRAMS, GRAMS, POUNDS, or OUNCES — and it lives on the variant, not normalized at product level. WooCommerce has one global weight unit in settings. I normalize everything to kg on import, but if the merchant has mixed units across variants (which I've seen), you're doing unit conversion inline per variant. Not hard, but completely undocumented as a gotcha.
              The pattern I noticed: the Storefront API is optimized for rendering a storefront fast. Anything that requires "give me a complete picture of this product" — inventory state, taxonomy membership, custom fields — either doesn't exist or requires restructuring your whole query strategy. It's an API built for reads, not for data portability.

Trending on Indie Hackers
Agencies charge $5,000 for a 60-second product demo video. I make mine for $0. Here's the exact workflow. User Avatar 149 comments I've been building for months and made $0. Here's the honest psychological reason — and it's not what I expected. User Avatar 143 comments This system tells you what’s working in your startup — every week User Avatar 43 comments 11 Weeks Ago I Had 0 Users. Now VIDI Has Reviewed $10M+ in Contracts - and I’m Opening a Small SAFE Round User Avatar 25 comments I built a health platform for my family because nobody has a clue what is going on User Avatar 15 comments Why Direction Matters More Than Motivation in Exam Preparation User Avatar 14 comments